Is conditionally rendering react child components a violation of SRP? - javascript

I'm fairly new to React and OOD so bear with me. Ideally I want to write my application in such a way that it will be easy to reason about and scale well. Rather, make my code as S.O.L.I.D as possible. I'm also concerned about testing my components as I'm new to TDD, as well.
So for example I've already tried writing my code very similar to this:
App.jsx - First Trial
// all other code omitted
class App extends Component {
constructor(props) {
super(props);
this.state = {
roundScore: 0,
lives: 1,
modal: true,
};
}
render() {
return (
<div className="App">
<Header
modal={this.state.modal}
lives={this.state.lives}
score={this.state.score}
/>
</div>
);
}
}
Header.jsx - First Trial
const Header = function(props) {
if (props.modal) {
return (<Logo logo={logo} />);
} else {
return (
<div>
<Lives lives={props.lives} />
<Score score={props.score} />
</div>
);
}
};
const Logo = function(props) {
return (
<div>
<img
src={props.logo}
className="logo logo--xs"
alt="logo"
/>
</div>
);
};
const Score = function(props) {
const text = (props.text ? <strong>{props.text}</strong> : '');
return (
<p className={props.styles}>
{text}
{props.score} pts
</p>
);
};
const Lives = function(props) {
return (
<p className="lives lives--left">
Lives:
{props.lives}
</p>
);
};
export default Header;
The reason I initially setup my code like this was because I had a lot of components and the App.jsx file and wanted to pull some of them out and nest them in wrapper components like <Header /> <Body /> <Footer />.
I began writing some unit tests with Jest and Enzyme for the above mentioned and found that I was having some difficulty testing some of the components in isolation. For example, testing the <Score /> component proved difficult unless I changed my export statement to something like export { Header, Scores, Lives, Logo }; but that doesn't seem like something I should do just for testing?
On top of that I have some propTypes which are required on these components so shallow rendering the <Header /> component and then passing props to <Score /> just to avoid the warnings in my console didn't seem right to me.
So instead I modified my code to the following:
App.jsx - Second Trial
class App extends Component {
constructor(props) {
super(props);
this.state = {
roundScore: 0,
lives: 1,
modal: true,
};
}
render() {
return (
<div className="App">
<Logo modal={this.state.modal} logo={logo} />
<Lives modal={this.state.modal} lives={this.state.lives} />
<Score modal={this.state.modal} score={this.state.roundScore} />
</div>
);
}
}
Header.jsx - Second Trial
const Logo = function(props) {
const styles = (props.modal ? 'logo logo--xs' : 'hide');
return (
<div>
<img
src={props.logo}
className={styles}
alt="I-Spell-Its logo"
/>
</div>
);
};
const Score = function(props) {
const styles = (props.modal ? 'hide' : props.styles)
const text = (props.text ? <strong>{props.text}</strong> : '');
return (
<p className={styles}>
{text}
{props.score} pts
</p>
);
};
const Lives = function(props) {
const styles = (props.modal ? 'hide' : 'lives lives--left')
return (
<p className={styles}>
Lives:
{props.lives}
</p>
);
};
export { Score, Logo, Lives };
Already I've found this setup easier to test, however now I have a few concerns:
Is it okay to use CSS to hide components depending on the application state? Will I be taking any performance hits?
I have several other parent/wrapper components that conditionally render child components depending on state and I feel it will be very difficult (but not impossible) to rewrite a lot of them. Should I bother or accept my mistakes and avoid this next time around?
Ultimately, I'm curious to know if conditionally rendering child components is a violation of Single Responsibility Principle?
Please don't flag this as a question that provokes discussion or opinions. I have done my research; I have read 3 books on React, taken one online course and read several articles and style guides, but I still have difficulty wrapping my head around how to best structure react applications. It would be nice to hear from some of the more experienced members of the community.

Related

How can I print an page with react-to-print

I need a print button on my form. I was doing an research for a library to do that, and I found this one:
https://www.npmjs.com/package/react-to-print
I was thinking about this flow: a component import and add a line in my code to call that component, and the print button works well, but as I understand it this component needs me to pass the component to be printed in full.
I tried to do this, but I got this error:
Attempted import error: './index' does not contain a standard export (imported as 'FormContent').
my index code:
const App = () => {
let numb_days = 22
return (
<div className="m-4">
<FormField label="Salário Base" show_small_text="false" numb_days={ numb_days }/>
<hr />
<h6 className="mb-4"> Qtd. dias úteis: { numb_days } </h6>
<FormField label="Auxilio Refeição" show_small_text="true" numb_days={ numb_days }/>
<FormField label="Auxilio Alimentação" show_small_text="true" numb_days={ numb_days }/>
<FormField label="Plano de Saúde" show_small_text="false" numb_days={ numb_days }/>
<FormField label="Outros Benefìcios (VT)" show_small_text="true" numb_days={ numb_days }/>
<ComponentToPrint ref={(el) => (this.componentRef = el)} />
</div>
);
};
my componente code:
import React, { Component } from "react";
import FormContent from "./index";
class ComponentToPrint extends Component {
render() {
return <FormContent ref={(el) => (this.componentRef = el)} />;
}
}
export default ComponentToPrint;
I think I must be making a big mistake, but I don't understand how I'm going to pass my index on to this component and call my index at the same time.
I found this example: https://codesandbox.io/s/interesting-cookies-k1bg9?file=/src/deliverySheet/ComponentToPrint.js
it looks like I need to follow the flow:
index -> print (my content).
but why couldn’t I do that? ->
index -> my content (print)
or
index -> my content AND print
I'm not really sure I understand your question but I'm going to try to answer it, the examples you gave is really hard to read because of all of the subfolders. Basicly what you need to do to print whit react to print is make a normal component and reference it on the same level.
I see you use class components, so im just going to copy and paste from the react-to-print docs.
import React from 'react';
import ReactToPrint from 'react-to-print';
import { ComponentToPrint } from './ComponentToPrint';
class Example extends React.PureComponent {
render() {
return (
<div>
<ReactToPrint
trigger={() => {
// NOTE: could just as easily return <SomeComponent />. Do NOT pass an `onClick` prop
// to the root node of the returned component as it will be overwritten.
return Print this out!;
}}
content={() => this.componentRef}
/>
<ComponentToPrint ref={el => (this.componentRef = el)} />
</div>
);
}
}
Where it says "trigger" is where you render your button.
I hope that helped.

How to get state values from custom component to another parent component

I have 2 classes (both React.Component). Let's say, that one of these is my own component, which also built on another custom component (in my case, it's React Places Autocomplete).
Just look at this picture
Code here:
//App.js
import React, { Component } from 'react';
import './App.css';
import PlaceAutocomplete from "./places_autocomplete";
class App extends Component {
constructor(props) {
super(props);
this.state = { output: '' };
}
render() {
return (
<div className="App">
<PlaceAutocomplete/>
<p>{this.state.output}</p>
</div>
);
}
}
export default App;
//places_autocomplete.js
import React, { Component } from 'react';
import './PlaceAutocomplete.css';
import PlacesAutocomplete from 'react-places-autocomplete';
class PlaceAutocomplete extends Component {
constructor(props) {
super(props);
this.state = { address: '', output: '' };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = async address => {
this.setState({address: address});
this.state.output = address;
document.getElementById('lsi').blur();
};
searchOptions = {
types: ['(cities)']
};
hidden = (suggest) => {
return suggest == null || suggest === ""
? "autocomplete-dropdown-container-hidden"
: "autocomplete-dropdown-container";
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
searchOptions={this.searchOptions}>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input value={this.state.address}
id={'lsi'}
{...getInputProps({
placeholder: 'Select a city',
className: 'location-search-input',
})}/>
<div className={this.hidden(suggestions[1])}>
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className: className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
export default PlaceAutocomplete;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
So, you can see how I tried to find a solution for this. This code mostly looks ugly, because I don't know any other way to implement these feautures.
System info:
The latest React for 17.08.2018 (I don't really remember, but I do know that it's the latest (I installed it just 1 week ago).
This application created by CRA (Create React Application) template. So please, if your solution won't work with this template (I think there's different styles, like ES6 etc. But it's not the point) then add at least an explanation to your answer.
Try lifting the state up to the parent component and use callbacks to share the data. As stated by the react docs, there should be a single "source of truth" for data changes in a React application - this reduces potential bugs and duplicated code. Take a look at . https://reactjs.org/docs/lifting-state-up.html

React.js - Wrap a shared component in different components based on media queries

I'm using react-responsive to get media queries and I want to have one component state being shared across screen sizes, but using different wrappers.
Example:
import MediaQuery from 'react-responsive';
import ReactSwipe from 'react-swipe';
const Desktop = props => <MediaQuery {...props} minWidth={992} />;
const Tablet = props => <MediaQuery {...props} minWidth={768} maxWidth={991} />;
const Mobile = props => <MediaQuery {...props} maxWidth={767} />;
export class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Desktop>
<SignUpForm />
</Desktop>
<Tablet>
<SignUpForm />
</Tablet>
<Mobile>
<ReactSwipe>
<SignUpForm />
</ReactSwipe>
</Mobile>
</div>
);
}
}
In this example, I want to use another component <ReactSwipe> to encapsulate <SignUpForm />. The above works, but it's creating 3 instances of SignUpForm... if you resize the browser and hit a breakpoint any form data you have filled out already will be lost as the new instance of SignUpForm loads. How do I change this to use media queries but one instance of <SignUpForm />.
Hm. I'm not familiar with MediaQuery, but I'd do this differently. I'd write / find a function that identifies the current platform and then switch based on that:
const wrappers = {
desktop: Desktop,
tablet: Tablet,
mobile: Mobile, // I'd have this wrapper do the ReactSwipe thing
};
export function App() {
// returns a string that is one of: 'desktop', 'tablet', 'mobile'
const platform = findPlatform();
const Wrapper = wrappers[platform];
return (
<Wrapper>
<SignUpForm />
</Wrapper>
);
}
Also, as you'll see above, I never use ES6 classes when a function will do. I try to use classes as infrequently as possible. This is personal preference, but I do find that it encourages me to write simpler code.
As asked, here's a possible (untested) implementation of findPlatform. I'd put this in its own module, so it can be mocked more easily during testing.
function findPlatform() {
const minTabletSize = 768; // Make this whatever you think is best
if (!(/Mobi/.test(navigator.userAgent))) {
return 'desktop';
}
if (window.outerHeight > minTabletSize || window.outerWidth > minTabletSize) {
return 'tablet';
}
return 'mobile';
}

switch components in react js

I'm doing a singlepage application and would like to switch a component.
Here is an image how it looks like:
If I click on the button in component 3, I will switch the component 3 with 5.
So maybe like component 3 is a view of all projects and if I click on one project, I will see a detail view of the project with some information.
I created two different components for this.
All other components should stay at the same place.
Here is my code how I switch the components:
this.state.detailVisible
? <ProjectDetailView/>
: null
I'm not sure if is the correct react way to do it. Also I have two different css files for component 3 and 5. If I'm switching the two component, I have some class name irritations with my css.
If it's a better way to do it with routers?
How is the react way to do it?
thanks for your help :)
It all depends on your needs, if you need to render both component3 and component5 then a route won't be much of a help.
If you need to render only one of them then a route can be handy.
as for the syntax i prefer this:
this.state.detailVisible && <ProjectDetailView/>
here is a simple example:
const components = ["Component1", "Component2", "Component3"];
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false
};
this.onComponentClicked = this.onComponentClicked.bind(this);
}
onComponentClicked(e) {
this.setState({
showDetails: !this.state.showDetails
});
}
render() {
const { name } = this.props;
const { showDetails } = this.state;
return (
<div>
<div>Component{name}</div>
<button onClick={this.onComponentClicked}>Toggle Details</button>
{showDetails && <ComponentDetails name={name} />}
</div>
);
}
}
const ComponentDetails = ({ name }) => (
<div>
<div>Details of component{name}</div>
</div>
);
class App extends React.Component {
render() {
return (
<div>
<h2>Parent</h2>
{components.map(c => {
return (
<div>
<Component name={c} />
<hr />
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Communicate two React children components with onClick functionality

Alright, I'm going to do my best to explain how my project is setup so that you can appropriately aid me on my quest to figure out how to approach this configuration.
I have a parent component that is a smart component. Through this component all my data from my store is being accessed.
class DashboardPage extends React.Component {
componentDidMount() {
this.props.getTips();
}
render() {
return (
<div>
<div className="row">
<div className="col-sm-7">
<ContentBox
title="The Scoop"
footerText="Submit a Story"
showSlider
content={<TipOfTheDay tips={this.props.tips} />}
/>
</div>
</div>
</div>
)
}
}
DashboardPage.propTypes = {
getTips: PropTypes.func
}
function mapStateToProps(state, ownProps) {
tips: state.tips
}
function mapDispatchToProps(dispatch) {
return {
getTips: () => { dispatch(tipActions.loadTips());} ## This hits tipActions and runs the `action creator`: loadTips(). Which returns all tips from api.
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);
As you can see, I have included two dumb components inside my smart component, <ContentBox/> & <TipOfTheDay/>. On the dashboardPage there are about 7 <ContentBox/> components, each inheriting special a title for the header/footer and also being told whether or not to display the footer through the showSlider boolean. Here is what <ContentBox/> looks like:
import React, {PropTypes} from 'react';
import Footer from './ContentBoxFooter';
const ContentBox = ({title, footerText, showSlider, content}) => {
return (
<div style={styles.root} className="col-sm-12">
<div style={styles.header} className="row">
<h3 style={styles.header.title}>{title}</h3>
<span style={styles.header.arrow} />
</div>
{content}
<Footer footerText={footerText} showSlider={showSlider} />
</div>
);
};
ContentBox.propTypes = {
title: PropTypes.string,
footerText: PropTypes.string,
showSlider: PropTypes.bool,
content: PropTypes.object
};
export default ContentBox;
And here is the footer:
import React, {PropTypes} from 'react';
import styles from './contentBoxStyles';
import Previous from './svg/Previous';
import Next from './svg/Next';
import Add from './svg/Add';
import consts from '../../styles/consts';
const ContentBoxFooter = ({footerText, showSlider}) => {
if (footerText != undefined) {
return (
<div style={styles.footer} className="row">
{
showSlider ?
<div>
<Previous fillColor={consts.orange} height="20px" width="20px"/>
<span style={styles.bar}>|</span>
<Next fillColor={consts.orange} width="20px" height="20px"/>
</div> : <div style={styles.emptyArrow} />
}
<div style={styles.footer.link}>
<span style={styles.footer.link.text}>{footerText}</span>
<Add fillColor={consts.orange} height="24px" width="24px" />
</div>
</div>
);
} else {
return(null);
}
};
ContentBoxFooter.propTypes = {
footerText: PropTypes.string,
showSlider: PropTypes.bool
};
export default ContentBoxFooter;
Few! So here is where I need to add the onClick functionality. This functionality needs to be added to the <Previous/> & <Next/> component that is an SVG. What I am attempting to do is create a slider for the tips that I have pulled in. Obviously there will be <Footer/> components that will need the same functionality, but controlling different data other than the tips. Because I am new to React & Redux, I am not sure how I can perform this and not just do it, but do it in the 'Redux` way.
How do I get these two svg components that are nested within other dumb components that are dumb components, to perform onClick functions for specific data on the page? I hope this made sense. For more clarity, here is what I am doing with the <TipOfTheDay/> component:
const tipOfTheDay = ({tips}) => {
return (
<div style={styles.tipBody} className="row">
{
tips.map(function(tip, key) {
return (
<div key={key} className="myTips">
<h3 style={styles.tipBody.header}>{tip.title}</h3>
<p style={styles.tipBody.content}>{tip.content}</p>
</div>
);
})
}
</div>
);
};
tipOfTheDay.propTypes = {
tips: PropTypes.array.isRequired
};
export default tipOfTheDay;
Thank you for anytime you spend reading/responded/assisting with this question. I am a fairly new developer and this is also new technology to me.
I'm not sure how you've implemented your Next and Previous Components, but since you've using React-Redux, you can create extra Containers to wrap those components and pass in a Redux Action to them, e.g.:
// PreviousComponent.jsx
var Previous React.createClass({
propTypes: {
goToPrevious: React.PropTypes.func,
},
render: function() {
return (
<div onClick={this.props.goToPrevious}>
...
</div>
);
}
};
export default Previous;
//PreviousContainer.jsx
import ReactRedux from 'react-redux';
import Previous from './PreviousComponent';
var mapStateToProps = (state, props) => {
return {};
};
var mapDispatchToProps = (dispatch) => {
return {
goToPrevious: () => {
dispatch(Actions.goToPrevious());
},
}
};
var PreviousContainer = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(Previous);
export default PreviousContainer;
By adding a container wrapper directly around your component, you can connect a redux action for going to the previous image/slide/whatever directly into your React component. Then, when you want to use the action in your ContentBoxFooter, you import the PreviousContainer and place it where you want the Previous component, e.g.:
//ContentBoxFooter.jsx
import PreviousContainer from './PreviousContainer'
const ContentBoxFooter = ({footerText, showSlider}) => {
if (footerText != undefined) {
return (
<div style={styles.footer} className="row">
{
showSlider ?
<div>
/*
* Add the PreviousContainer here where before you were only using your regular Previous component.
*/
<PreviousContainer fillColor={consts.orange} height="20px" width="20px"/>
<span style={styles.bar}>|</span>
<Next fillColor={consts.orange} width="20px" height="20px"/>
</div> : <div style={styles.emptyArrow} />
}
<div style={styles.footer.link}>
<span style={styles.footer.link.text}>{footerText}</span>
<Add fillColor={consts.orange} height="24px" width="24px" />
</div>
</div>
);
} else {
return(null);
}
};
ContentBoxFooter.propTypes = {
footerText: PropTypes.string,
showSlider: PropTypes.bool
};
By wrapping both the Next and Previous components in containers that pass actions into them, you can connect the Redux actions directly into your components without having to pass them from the root component of your application. Also, doing this allows you to isolate where certain actions are called. Your Previous button is probably the only component that would want to call a Previous action, so by placing it in a Container wrapper around the component, you're making sure that the Previous action is only used where it is needed.
Edit:
If you have to deal with multiple actions, it is better to define them at a higher level. In this case, since the ContentBox is the common breaking point, I would define separate Previous actions for each type of content box and pass them into each ContentBox instance:
var DashboardApp = React.createClass({
propTypes: {
TipsPrevious: React.PropTypes.function,
NewsPrevious: React.PropTypes.function,
},
render: function() {
<div>
<ContentBox
previousAction={this.props.TipsPrevious}
contentType='Tips'
/>
<ContentBox
previousAction={this.props.NewsPrevious}
contentType='News'
/>
...
</div>
},
});
Pass the actions down through the child components until you reach the Previous component and then attach the action to an 'onClick' handler on the Previous component.
The idea here behind this is that you want to limit the scope of parameters to the least amount of code possible. For example, if you added a profile component showing your user information on the page, you might want to add a container around that component and pass in the User-related information/actions without passing the information to the rest of your application. By doing this, it makes it easier to focus information where it is needed. It also helps you figure out where some change/action is taking place in your code if you have to fix a bug.
In the example above, if the Dashboard component is your root component, you'll just pass the Redux Actions into through a container wrapping it. However, if your dashboard component is a nested component itself, pass the actions into it through a custom container so that the actions aren't spread to code that don't need to see it:
<AppRoot>
<PageHeader/>
<DashboardContainer />
<PageSidebar />
<PageFooter />
</AppRoot>

Categories

Resources