How to organize functionality across react components? - javascript

I have some React Components:
Row component, here is the snippets:
export default Row() {
return (
<>
<div className="grid grid-4-cols">
// Here is the cell details
...
// Code below are one of the cell
<div>
<button id="show-modal" type="button" onclick={showModal}></button>
<div>
...
<div>
</>
);
}
Modal component
export default Modal() {
return (
// Below are elements in the modal
<div>
...
</div>
);
}
Table component
import Row from "./Row";
import Modal from "./Modal";
export default function Table {
// I am trying to use React Hook
const [modalOn, setModalOn] = useState(false);
return (
<>
<div>
// Some table features
<Row/>
<Row/>
</div>
{modalOn && <Modal/>}
</>
)
}
I want to implement so that when show-modal button being clicked, the modalOn are set to true. Where to implement this showModal function?

So, you need to declare the showModal function in the Table component, which will change modalOn state to true, and pass it as prop to the Row components.
Row component
export default Row({ showModal }) {
return (
<>
<div className="grid grid-4-cols">
// Here is the cell details
...
// Code below are one of the cell
<div>
<button id="show-modal" type="button" onclick={showModal}></button>
<div>
...
<div>
</>
);
}
Modal component
export default Modal() {
return (
// Below are elements in the modal
<div className="hidden">
...
</div>
);
}
Table component
import Row from "./Row";
import Modal from "./Modal";
export default function Table {
// I am trying to use React Hook
const [modalOn, setModalOn] = useState(false);
const showModal = () => {
setModalOn(true)
}
return (
<>
<div>
// Some table features
<Row showModal={showModal}/>
<Row showModal={showModal} />
</div>
{modalOn || <Modal/>}
</>
)
}

Ideally As per React it is recommended to lift up the state as & when possible so acc to me your should keep separate component as model component.. & you pass props to open close to this component..
Also you can make this modal reusable if u are willing to accept content as a children.

Related

Optimal way to set state of another component from

there's a few posts explaining how to update state from another component but I'm still unable to parse the solution for my particular problem. I want the submit button in my SideBar.js component to trigger nextProblem() in my tableA.js component when it is clicked. This will cause the state of tableA.js to change as problemNum goes from 1 to 2, generating a different form. I can't import SideBar.js component into my tableA.js due to my design layout though! SideBar.js also contains images. Any ideas?
I'm using a CSS grid for the layout of my page:
grid-template-areas:
"navbar navbar navbar navbar"
"sidebar tableA ... ..."
"sidebar tableA ... ..."
"sidebar tableA ... ..."
I'm using App.js to route to the pages:
function App() {
return (
<Router>
<div className="App">
<Routes>
<Route path={"/"} exact element={<MainPage/>}/>
<Route path={"/gamePage"} exact element={<GamePage/>}/>
</Routes>
</div>
</Router>
);
}
export default App;
I created a SideBar.js component that contains a logo at the top and a submit button at the bottom:
class SideBar extends Component {
componentDidMount() {
}
render() {
return (
<div className={"sidebar"}>
<div className={"logo_group"}>
<img className={"some_logo"} src={logo}/>
</div>
<div className={"nav_group"}>
<button className={"home"}/>
<button className={"about"}/>
</div>
<img className={"an_image"} src={someImage}/>
<div className={"button_group"}>
<button className={"submit_button"} onClick={() => nextProblem()}>Submit</button>
</div>
</div>
)
}
}
export default SideBar
and a tableA.js component with a function nextProblem() that changes the state of the component:
class tableA extends Component {
state = {
problemNum: 1,
problemStatus: 'unsolved',
userAnswer: []
};
nextProblem = () => {
this.setState(state => ({
problemNum: state.problemNum + 1,
}))
}
componentDidMount() {}
render() {
return (
<div>
<form>
...depends on the state of the problem number
</form>
</div>
)
}
export default tableA
I then consolidate everything in GamePage.js:
const GamePage= () => {
return (
<div className={"gamepage-container"}>
<div className={"navbar"}>
<NavBar></NavBar>
</div>
<div className={"sidebar"}>
<SideBar></SideBar>
</div>
<div className={"tableA"}>
<TableA></TableA>
</div>
...
</div>
)
}
export default GamePage
i'd propably go the approach of letting the gamepage handle the state and such and send down the problem and the update function down the components as props. Depending on how big this thing will grow you could also use a centralized store.
const GamePage= () => {
const [problem, setPropblem] = useState(0)
return (
<div className={"gamepage-container"}>
<div className={"navbar"}>
<NavBar></NavBar>
</div>
<div className={"sidebar"}>
<SideBar
onSetNext={() => setProblem(problem + 1)}
></SideBar>
</div>
<div className={"tableA"}>
<TableA
problem={problem}
></TableA>
</div>
...
</div>
)
}
export default GamePage
and in your sidebar the button becomes
<button className={"submit_button"} onClick={() => this.props.onSetNext()}>Submit</button>
similarly you access the props.propblem instead of state in your table.

Passing React State

I have a modal that I want to appear on a button click. This works fine, however, in order for it to be positioned correctly, I need to move the modal component to be rendered in a parent component.
I'm unsure how to do this as I'm not sure how I would pass the state down to the component where the button click is created. I'm also unsure of whether this is about passing a state or using a button onClick outside the component the state is defined in.
My code works and does what I want, but, I need <DeleteWarehouseModal show={show} /> to be in the parent component below. How can I do this?
The parent component where I want to render my modal:
function WarehouseComponent(props) {
return (
<section>
<div>
<WarehouseListItems warehouseData={props.warehouseData} />
//Modal component should go here
</div>
</section>
);
}
The component (WarehouseListItems) where the modal is currently being rendered:
function WarehouseListItem(props) {
const [show, setShow] = useState(false);
return (
<>
//some code necessary to the project, but, irrelevant to this issue
<Link>
<div onClick={() => setShow(true)}></div>
</Link>
<DeleteWarehouseModal show={show} />
</>
);
}
The modal:
const DeleteWarehouseModal = (props) => {
if (!props.show) {
return null;
}
return (
//some elements here
);
};
Yes, you can move the state and it's handler in WarehouseComponent component and pass the handler down to child component, which can change the state in parent component.
WarehouseComponent :-
function WarehouseComponent(props) {
const [show, setShow] = useState(false);
const toggleShow = () => {
setShow(state => !state);
}
return (
<section>
<div>
<WarehouseListItems
warehouseData={props.warehouseData}
toggleShow={toggleShow}
/>
<DeleteWarehouseModal show={show} />
</div>
</section>
);
}
WarehouseListItems : -
function WarehouseListItem(props) {
const {toggleShow} = props;
return (
<>
//some code necessary to the project, but, irrelevant to this issue
<Link>
<div onClick={() => toggleShow()}></div>
</Link>
</>
);
}

React render list only when data source changes

Basically I have a modal with a state in the parent component and I have a component that renders a list. When I open the modal, I dont want the list to re render every time because there can be hundreds of items in the list its too expensive. I only want the list to render when the dataSource prop changes.
I also want to try to avoid using useMemo if possible. Im thinking maybe move the modal to a different container, im not sure.
If someone can please help it would be much appreciated. Here is the link to sandbox: https://codesandbox.io/s/rerender-reactmemo-rz6ss?file=/src/App.js
Since you said you want to avoid React.memo, I think the best approach would be to move the <Modal /> component to another "module"
export default function App() {
return (
<>
<Another list={list} />
<List dataSource={list} />
</>
);
}
And inside <Another /> component you would have you <Modal />:
import React, { useState } from "react";
import { Modal } from "antd";
const Another = ({ list }) => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<Modal
visible={showModal}
onCancel={() => setShowModal(false)}
onOk={() => {
list.push({ name: "drink" });
setShowModal(false);
}}
/>
<button onClick={() => setShowModal(true)}>Show Modal</button>
</div>
)
}
export default Another
Now the list don't rerender when you open the Modal
You can use React.memo, for more information about it please check reactmemo
const List = React.memo(({ dataSource, loading }) => {
console.log("render list");
return (
<div>
{dataSource.map((i) => {
return <div>{i.name}</div>;
})}
</div>
);
});
sandbox here

Wrong pass prop in react component

I have simple Reactjs app that includes the Card and Modal components. every Card must have a Modal that when clicking on "Show More" button, open it.
Modal should only show the title on its Card and my problem is passed props to Modal, just send the title of the last Card And not about itself!
In summary, the prop of title received properly in Card component but Card component can't pass title to Modal correctly.
Here is my app in CodeSandBox: Demo
Card Components:
const Card = props => {
const { children, title } = props;
const { isShowModal, setIsShowModal } = useContext(MainContext);
const showModalHandler = () => {
setIsShowModal(true);
};
return (
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
<div className="card-content">{children}</div>
<div className="card-footer">
<button onClick={showModalHandler}>Show More</button>
</div>
{isShowModal && <Modal title={title} />}
</div>
);
};
Modal Component:
const Modal = props => {
const { setIsShowModal } = useContext(MainContext);
const closeModalHandler = () => {
setIsShowModal(false);
};
const { title } = props;
return (
<div className="modal">
<h2>Modal: {title}</h2>
<p>
You cliked on <b>{title}</b> Card
</p>
<hr />
<button onClick={closeModalHandler}>Close</button>
</div>
);
};
Note: I use Context for control open/close modal in isShowModal state and maybe that's the problem?
Just as you thought the problem seems to be the useContext that you are using. So I made a couple of changes to the code, most importantly using useState. I recommend you read the documentation about useContext and when to use it. Here is the updated code:
Card.js
import React, { useState } from "react";
import Modal from "./Modal";
import "./Card.scss";
const Card = props => {
const { children, title } = props;
const [ isShowModal, setIsShowModal ] = useState(false);
return (
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
<div className="card-content">{children}</div>
<div className="card-footer">
<button onClick={() => setIsShowModal(true)}>Show More</button>
</div>
{isShowModal && <Modal setIsShowModal={setIsShowModal} title={title} />}
</div>
);
};
export default Card;
Modal.js
import React from "react";
import "./Modal.scss";
const Modal = props => {
const { title } = props;
return (
<div className="modal">
<h2>Modal: {title}</h2>
<p>
You cliked on <b>{title}</b> Card
</p>
<hr />
<button onClick={() => props.setIsShowModal(false)}>Close</button>
</div>
);
};
export default Modal;
As you can see, Modal.js component doesn't have to be a stateful component. You can pass as a prop the setIsShowModal function from Card.js component. That way you can make the modal a reusable component.

Extracting HTML from a stateless React component that's been passed into a trigger event

I'm trying to create a reusable modal that is rendered high in the DOM (direct child of <body>), and gets content passed to it from wherever.
I have to set the state of the modal with something like a trigger event (unless I'm overlooking another option). Redux is not an option, as I don't have it in the app.
My problem is that when I pass the component containing the content into the trigger event, it renders just the object, but none of the html. It makes sense to me why it works like this, but I can't seem to find a way to extract the content from that object.
My modal:
import React from "react"
import PropTypes from "prop-types"
import Rodal from 'rodal'
class ApplicationModal extends React.Component {
state = {
modalIsOpen: false,
htmlContent: ""
}
componentDidMount() {
$(window).on('modalToggle', (e, content) => {
this.modalToggle(() => this.setModalContent(content))
})
}
setModalContent = (content) => {
this.setState({htmlContent: content})
}
modalToggle = (callback) => {
this.setState({modalIsOpen: !this.state.modalIsOpen}, callback())
}
modalClose = () => {
this.setState({modalIsOpen: false})
}
modalOpen = () => {
this.setState({modalIsOpen: true})
}
render () {
return (
<React.Fragment>
<Rodal visible={this.state.modalIsOpen} onClose={this.modalClose} closeOnEsc={true} className={this.props.rodalClasses}>
<div id="modal-container">
<div dangerouslySetInnerHTML={{__html: this.state.htmlContent}}></div>
</div>
</Rodal>
</React.Fragment>
);
}
}
export default ApplicationModal
My page:
import React from "react"
import PropTypes from "prop-types"
class MyPage extends React.Component {
render () {
// This works
const html = `
<div className="modal-content">
<p>This is a question.</p>
<p>This is an answer.</p>
</div>
`
// This does not work
const ModalContent = () => (
<div className="modal-content">
<p>This is a question.</p>
<p>This is an answer.</p>
</div>
)
return (
<React.Fragment>
<h1>My page</h1>
{/* This works */}
<a href='javascript:void(0)' onClick={() => $(window).trigger('modalToggle', html)}>Learn more</a>
{/* This does not work */}
<a href='javascript:void(0)' onClick={() => $(window).trigger('modalToggle', <ModalContent/>)}>Learn more</a>
</React.Fragment>
);
}
}
export default MyPage
I'd like to be able to pass full components into the trigger event, so the content can be rendered with buttons and dynamic inputs.
Am I going about this completely wrong?
In case it's not obvious, Rodal is just a pretty modal library.
What you're describing can be accomplished with Reactstrap (A bootstrap library specific for React) and the built in component Modal:
import React from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
const ApplicationModal = props => {
const modalBody = (
<div className="modal-content">
<p>This is a question.</p>
<p>This is an answer.</p>
</div>
);
return (
<div>
<Button color="danger" onClick={props.toggle}>
{props.props.buttonLabel}
</Button>
<Modal
isOpen={props.modal}
toggle={props.toggle}
className={props.className}
>
<ModalHeader toggle={props.toggle}>Modal title</ModalHeader>
<ModalBody>{modalBody}</ModalBody>
<ModalFooter>
<Button color="primary" onClick={props.toggle}>
Do Something
</Button>{" "}
<Button color="secondary" onClick={props.toggle}>
Cancel
</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default ApplicationModal;
Turns out the dangerouslySetInnerHTML was messing it up. I guess it was trying to render straight HTML, rather than rendering the component I passed in as an executable function. Which... Makes sense now that I think about it...

Categories

Resources