ReactJS: Separating Components Best Practices - javascript

So, I have a react-bootstrap nav and I want to have one of the nav items open and close a bootstrap modal component.
I have this working with this:
import React, { Component, render } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem, Modal, Button } from 'react-bootstrap';
export default class NavigationBar extends Component {
constructor() {
super();
this.state = {
showModal: false
}
}
close() { this.setState({ showModal: false }); }
open() { this.setState({ showModal: true }); }
render() {
return (
<div>
<Navbar>
...entire navbar...
</Navbar>
<Modal show={this.state.showModal} onHide={() => this.close()}>
...entire modal... which would be nice to put if a different file
</Modal>
</div>
); } }
Ideally, I would like to put the modal in a different component file and import it in, but when I do, I'm lost on how to translate the navbar open and close.
What is the best practice for combining components while maintaining their state across files?
Thanks!

A good way to think about it is containers vs presentational components. Containers hold your state and most of your logic. Presentational components take in inputs (props) and render html (jsx) [and do little else].
So, you could make your own Modal component that takes in the methods to call on close and one on whether or not it's shown. It could even be a stateless component - if it's just props + jsx, no need for a full class structure:
import React, { PropTypes } from 'react';
const MyModal = ({ show, onHide}) => (
<Modal show={show} onHide={onHide}>
// ...entire modal...
</Modal>
);
// displayName and propTypes are always good to have
MyModal.displayName = 'MyModal';
MyModal.propTypes = {
show: PropTypes.bool.isRequired,
onHide: PropTypes.func.isRequired,
};
export default MyModal;
then to use it, you will need to make sure to bind your methods so they're called in the right context:
class NavigationBar extends Component {
constructor() {
super();
this.state = {
showModal: false
};
// this is the important binding
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
close() { this.setState({ showModal: false }); }
open() { this.setState({ showModal: true }); }
render() {
return (
<div>
<Navbar>
// ...entire navbar...
</Navbar>
<MyModal
show={this.state.showModal}
onHide={this.close}
>
// child content if needed (unless it's all defined in MyModal)
</Modal>
</div>
);
}
}

You can wrap your react-bootstrap Modal with your content into your own custom component like so:
import React from 'react';
import { Modal } from 'react-bootstrap';
const NewModal = ({show, onHide}) => {
<Modal show={show} onHide={onHide}>
Modal content in here
</Modal>
};
export default NewModal;
And then import that modal from your component file
import Modal from 'components/modal' // Import your new modal's default export

Related

Display a new component with button click

Im making my first react ptoject. Im new in JS, HTML, CSS and even web app programing.
What i try to do, is to display some infomration on button click.
I have an API, that looks like this:
endpoint: https://localhost:44344/api/Projects
My Data from it:
[{"id":1,"name":"Mini Jira","description":"Description for first project in list","tasks":null},{"id":2,"name":"Farm","description":"Description for second one","tasks":null}]
And im fine with that, i can get it easily by axios in my react app.
Now i will show you my Project.js Component:
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={Here i want to display new component with details }bsStyle="primary">Details</Button>
</ButtonToolbar>
);
}
}
export default Project;
I have all data from api in project type.
My question is, how to display component that i named ProjectDetails.js on button click? I want to show all data stored in project from my api in separate view (new page or somethig like that).
View looks like this:
Thanks for any advices!
EDIT:
based on #Axnyff answer, i edited Project.js. it works ok. But when i want to (for testing) displat project.name, i get error map of undefined. My ProjectDetails.js:
import React, { Component } from "react";
class ProjectDetails extends Component {
state = {};
render() {
return <li>{this.props.project.name}</li>;
}
}
export default ProjectDetails;
EDIT2:
In Project.js in #Axnyff answet i just edited that line:
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
i passed project by props, now it works like i want too. After click it displays project.name that i clicked on.
You should use state in your React component.
Let's create a field called showDetails in your state.
You can initialize it in your constructor with
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
Then you need to modify the onClick to set that state to true
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
And then use that state to show or not the ProjectDetails:
{ showDetails && <ProjectDetails /> }
The full component should look like
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
{ this.state.showDetails && <ProjectDetails /> }
</ButtonToolbar>
);
}
}
export default Project;
You can then modify the logic to add a toggling effect etc.
If you haven't done it, you should probably follow the official tutorial
function Bar() {
return <h1>I will be shown on click!</h1>;
}
class Foo extends React.Component {
constructor() {
super();
this.state = { showComponent: false };
}
handleClick = () => {
this.setState({ showComponent: !this.state.showComponent });
};
render() {
return (
<div>
{this.state.showComponent && <Bar />}
<button onClick={this.handleClick}>click</button>
</div>
);
}
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React.js setState does not update state even if render method was called

I cannot believe that I cannot update state by setState.
I want to update cardModalOpen state to close the Modal.
I add bind(this) but it still does not work.
(Modal is opened by click Card Component)
However, I did setState({cardModalOpen: false}) by closeModal() function but it is still true even after render method was called.
Can someone please explain what I am doing wrong.
This is my code.
index.js
import React, { Component }from 'react';
import { Button, Card, Image, Header, Modal, Form, Input } from 'semantic-ui-react'
class App extends React.Component {
state = { cardModalOpen:false }
showCardModal() {
this.setState({cardModalOpen:true})
}
closeModal(){
this.setState({cardModalOpen:false})
}
render() {
const messagesDataNew = [];
for (var i = 0; i < 3; i++) {
messagesDataNew.push(
<Card
onClick={() => {
this.showCardModal();
}}
>
<DetailModal
cardModalOpen={this.state.cardModalOpen}
closeModal={this.closeModal}
/>
</Card>
);
}
return <div>{messagesDataNew}</div>;
}
}
DetailModal.js
import React, { Component }from 'react';
import { Button, Card, Image, Header, Modal, Form, Input } from 'semantic-ui-react'
class DetailModal extends Component{
render(){
return(
<Modal open={this.props.cardModalOpen} onClose={()=>{this.props.closeModal()}} >
<Modal.Header>Select a Photo</Modal.Header>
<Modal.Content image>
<Image wrapped size='medium' src='https://react.semantic-ui.com/images/avatar/large/rachel.png' />
<Modal.Description>
<Header>Default Profile Image</Header>
<p>We've found the following gravatar image associated with your e-mail address.</p>
<p>Is it okay to use this photo?</p>
</Modal.Description>
</Modal.Content>
<Button onClick={()=>{this.props.closeModal()}}>Close</Button>
</Modal>
)
}
}
export default DetailModal;
Here is a codesandbox with issue reproduced https://codesandbox.io/s/jjk7nw647y
In codesandbox you shared there is no clickable trigger-element for any of the modals. That's because of you are rendering an empty content of Card.
Here is my changes to your example https://codesandbox.io/s/l97n95n2om
The only difference is line #20 - I added a text Some text so your Card component has valid visible (and clickable) DOM element.
Do not forget bind your state handler functions:
constructor(props){
super(props)
this.showCardModal=this.showCardModal.bind(this)
this.closeModal =this.closeModal.bind(this)
}

React: How to invoke a modal if components are unrelated

I'm trying to get a modal to launch, however, with the current set-up, the components do not have a parent-child relationship and are completely unrelated. Is there any way to do this? I know the ideal is to have them in a parent-child set up, but this situation calls for them to be unrelated. I need the two buttons in App.js to be able to launch the modal, just like the buttons in Modal.js already can. Any help or thoughts would be appreciated.
App.js:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import InputComponent from './components/input_component';
import Modal from './components/modal';
class App extends Component {
constructor(props){
super(props);
}
componentDidMount(){
this.refs.modal.showFunction();
this.refs.modal.hideFunction();
}
state = {
fields: {},
};
onChange = updatedValue => {
this.setState({
fields: {
...this.state.fields,
...updatedValue,
}
});
};
render() {
return (
<div>
<InputComponent onChange={fields => this.onChange(fields)}/>
<p>{JSON.stringify(this.state.fields)}</p>
<Modal container={this} ref="modal" />
<button onClick={this.showFunction}>click to trigger show modal from App</button>
<button onClick={this.hideFunction}>click to trigger hide modal from App</button>
</div>
);
}
}
export default App;
Modal.js:
import React from 'react';
import '../styles/styles.css';
export default class Modal extends React.Component {
constructor(props){
super(props)
this.state = {
show: false,
}
this.showFunction = this.showFunction.bind(this);
this.hideFunction = this.hideFunction.bind(this);
}
showFunction(){
this.setState({
show: true,
})
}
hideFunction(){
this.setState({
show: false,
})
}
render(){
if(!this.state.show){
return <button onClick={this.showFunction}>showModal</button>
}
return(
<div className="modal-styles">
<Modal show={this.state.show} container={this.props.container}>
<h2>This will be the Modal</h2>
</Modal>
<button onClick={this.hideFunction}>hideModal</button>
</div>
);
}
}
If the two components are coming from two totally separate places, this could be a fringe case where it's most practical to just use the window variable. Note that the /* GLOBAL window.etc */ syntax here isn't for ESLint, just for clarity.
App.js
/* GLOBAL window.__showModal */
/* GLOBAL window.__hideModal */
// class App...
showFunction() {
if (window.__showModal) {
window.__showModal();
} else {
// Handle errors: Other component has not mounted
}
}
hideFunction() {
if (window.__hideModal) {
window.__hideModal();
} else {
// Handle errors: Other component has not mounted
}
}
Modal.js
/* GLOBAL window.__showModal */
/* GLOBAL window.__hideModal */
// class Modal...
componentDidMount() {
window.__showModal = () => this.showFunction()
window.__hideModal = () => this.hideFunction()
}
componentDidUnmount() {
// Kill references to 'this'
window.__showModal = undefined
window.__hideModal = undefined
}

Add loader on button click in react/redux application

I'm trying to add a Loader as Higher-Order-Component on button click in react/redux application.
Already have working Loader component and styling, just need to set logic when button is clicked show loader and hide existing button.
Button component:
import React from 'react'
import '../../../styles/components/_statement-print.scss';
import Loader from './Loader';
const StatementPrint = (props) => {
return (
<div>
<button
className="print-statement-button"
onClick={props.handleStatementPrint}>PRINT
</button>
</div>
);
};
export default Loader(StatementPrint);
Loader:
import React, { Component} from 'react';
import '../../../styles/components/_loader.scss';
const Loader = (WrappedComponent) => {
return class Loader extends Component {
render() {
return this.props.handleStatementPrint // Where must be logic when to show loader or existing button component
? <button className="loader-button">
<div className="loader">
<span className="loader-text">LOADING...</span>
</div>
</button>
: <WrappedComponent {...this.props} />
}
}
}
export default Loader;
In Loader component i added comment where need to write logic when to set loader or button.
I followed this example: ReactCasts - Higher Order Components
I searched a lot of examples but most of them shows how to set loader then is data is fetching, but in my case i just need to show then onClick method is triggered.
So how to set logic when onClick method is fired? Is this is a good aproach? Also it will be better to try acomplish this doing with redux state, but don't know how to do this.
Any help will be appreciated.
You will have to make small modifications to achieve what you want.
The wrapper component Loader can have a isLoading state, on the basis of which you can decide whether to show the loader span or the wrapped component.
This state isLoading can be updated by the wrapped component by passing showLoader function as a prop.
Button component
import React from 'react'
import '../../../styles/components/_statement-print.scss';
import Loader from './Loader';
const StatementPrint = ({handleStatementPrint, showLoader}) => {
return (
<div>
<button
className="print-statement-button"
onClick={() => {
showLoader();
handleStatementPrint();
}}>
PRINT
</button>
</div>
);
};
export default Loader(StatementPrint);
Loader
import React, { Component} from 'react';
import '../../../styles/components/_loader.scss';
const Loader = (WrappedComponent) => {
return class Loader extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
}
this.showLoader = this.showLoader.bind(this);
}
showLoader() {
this.setState({isLoading: true});
}
render() {
return this.state.isLoading
? <button className="loader-button">
<div className="loader">
<span className="loader-text">LOADING...</span>
</div>
</button>
: <WrappedComponent
{...this.props}
showLoader={this.showLoader}
/>
}
}
}
export default Loader;
EDIT
Since handleStatementPrint was required to be called, I have updated the click handler to include that function.
Also using de-structuring to avoid typing props repeatedly. See here for more info.
Just some external state is needed.
If you can't have external state (eg isLoading) than you could pass a function into a loader hoc which will derive isLoading from current props
Example: https://codesandbox.io/s/8n08qoo3j2

How to toggle elements with a button press in React?

I have searched for 3 freaking days to find out how to toggle my mobile nav button to toggle my mobile menu. I am new to React and could do this easily with jQuery but I don't want to use jQuery. I have line for line copied an example that I found on how to show or hide an element. I can not get it to work. Any help would be much appreciated. I am using styled-components with React.
Button sub-component:
import React, { Component } from 'react';
class MenuButton extends Component {
render() {
return (
<Button onClick={this.props.toggleMenu}>
<Menu></Menu>
</Button>
)
}
}
export default MenuButton;
Menu sub-component:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
class Menu extends Component {
render() {
return (
<OffCanvasMenu>
<Title>Menu</Title>
<Nav>
<NavLinks><Link to='/'>Home</Link></NavLinks>
<NavLinks><Link to='/about'>About</Link></NavLinks>
<NavLinks><Link to='/interactive'>Interactive</Link></NavLinks>
<NavLinks><Link to='/ideas'>Ideas</Link></NavLinks>
<NavLinks><Link to='/contact'>Contact</Link></NavLinks>
</Nav>
</OffCanvasMenu>
)
}
}
export default Menu;
Menu Container component with all the state:
import React, { Component } from 'react';
import Menu from './Menu';
import MenuButton from './MenuButton';
class MenuContainer extends Component {
constructor(props) {
super(props);
this.state = {
active: false
}
this.toggleMenu = this.toggleMenu.bind(this);
}
toggleMenu() {
const { active } = this.state;
this.setState({
//toggle value of `active`
active: !active
});
}
render() {
return (
<div>
<MenuButton onClick={this.toggleMenu}/>
{this.state.active && <Menu/>}
</div>
)
}
}
export default MenuContainer;
I can see a checkbox in ReactDev tools that shows MenuContainer has state but when the button is clicked it does not toggle the state.
onClick is handled by MenuButton component which in turns invokes toggleMenu function passed as a property. I would pass toggleMenu as property of MenuButton:
<MenuButton toggleMenu={this.toggleMenu} />

Categories

Resources