Optimal way to set state of another component from - javascript

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.

Related

How to organize functionality across react components?

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.

Create and download image of a react component without rendering it

My use case is simple. I have a react component that takes some props and renders something. Now I want to download it as an image without rendering it basically not showing it to the user.
I have tried html2canvas and react-component-export-image. In both of these libraries, I managed to capture the screenshot but both of them required me to render the component.
I used code from this page for react-component-export-image https://www.npmjs.com/package/react-component-export-image
Suppose the following is my component
const Component1 = ({ reference }) => {
return (
<div ref={reference} >
<div className="share-cause-header">
Some header stuff
</div>
<div className="share-cause-body">
some body stuff
</div>
<div className="share-cause-footer">
some footer stuff
</div>
</div>
);
};
Now on another Component2, I want to send some props to my Component1 and then download that component as an image without showing anything to the user. A user should only see the download button and the downloaded image
const Component2 = () => {
const shareButtonImageDownload = (e) => {
e.stopPropagation();
console.log("Hi there");
};
return (
<div >
<button onClick={shareButtonImageDownload } ></button>
</div>
);
};
If the component is not visible, then you can't take a screenshot of it. But you can bring them out of the visible view, like this:
https://codesandbox.io/embed/laughing-http-w3ndr?fontsize=14&hidenavigation=1&theme=dark
import {
exportComponentAsJPEG,
exportComponentAsPDF,
exportComponentAsPNG
} from "react-component-export-image";
import React, { useRef } from "react";
const ComponentToPrint = React.forwardRef((props, ref) => (
<div ref={ref} style={{ marginTop: "-50px" }}>
<div className="share-cause-header">Some header stuff</div>
<div className="share-cause-body">some body stuff</div>
<div className="share-cause-footer">some footer stuff</div>
</div>
));
const MyComponent = () => {
const componentRef = useRef();
return (
<React.Fragment>
<ComponentToPrint ref={componentRef} />
<button onClick={() => exportComponentAsJPEG(componentRef)}>
Export As JPEG
</button>
<button onClick={() => exportComponentAsPDF(componentRef)}>
Export As PDF
</button>
<button onClick={() => exportComponentAsPNG(componentRef)}>
Export As PNG
</button>
</React.Fragment>
);
};
export default MyComponent;

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...

React-router-dom redirect issue

i'm working on this project that's an implementation of youtube, let's say i search for 'Sia' for example at '/' i get the result back with videos,channels,playlists and when i click on the channel item i route to '/channel' with the channel component now the problem is , when i search for something while at /channel i'm supposed to redirect back to '/' and get the search results with the submitted search term. but i have no idea what's going wrong or if it's a good idea wheather to make the Header component a direct child of the BrowserRouter or render it in each route component along with it's props (which what i went for anyway)
here's the channel component and routing
class ChannelDisplay extends React.Component {
onFormSubmit = (term) => {
this.props.fetchList(term);
this.props.defaultVideo(term);
}
renderHeader() {
const {channel} = this.props
if(!channel.snippet) return <Search/>
if(channel) {
const subNum = `${Number(channel.statistics.subscriberCount).toLocaleString()}`
return (
<div className="channel">
<Header onFormSubmit={this.onFormSubmit}/>
<div className="container">
<img className="img-fluid" src={channel.brandingSettings.image.bannerImageUrl} alt={channel.snippet.title} />
<div className="d-flex flex-nowrap">
<img className="img-thumbnail img-fluid channel-img mx-2 my-2" src={channel.snippet.thumbnails.default.url} alt={channel.snippet.title} />
<div className="media-content">
<p>{channel.snippet.title}</p>
<span><i className="fab fa-youtube mr-2"></i> Subscribe {subNum}</span>
</div>
</div>
</div>
</div>
)
}
}
render() {
return this.renderHeader()
}
}
const mapStateToProps = state => {
return {channel:state.channel}
}
export default connect(mapStateToProps,{fetchList,defaultVideo})
(ChannelDisplay)
.
render() {
return (
<div>
<BrowserRouter>
<div>
<Route path="" exact component={Search} />
<Route path="/channel" exact component={ChannelDisplay} />
</div>
</BrowserRouter>
</div>
)
}
entire code https://github.com/IslamGamal88/minitube
Maybe you should add history.push or history.replace into your submit function in Search.js file, but I think the push is a much better option because you will be able to go back with back button to your channel or video or something.
onFormSubmit = (term) => {
this.props.fetchList(term);
this.props.defaultVideo(term);
this.props.history.push('/');
};

Categories

Resources