Is there a way to only add attributes to a React component if a certain condition is met?
I'm supposed to add required and readOnly attributes to form elements based on an Ajax call after render, but I can't see how to solve this since readOnly="false" is not the same as omitting the attribute completely.
The example below should explain what I want, but it doesn't work.
(Parse Error: Unexpected identifier)
function MyInput({isRequired}) {
return <input classname="foo" {isRequired ? "required" : ""} />
}
Apparently, for certain attributes, React is intelligent enough to omit the attribute if the value you pass to it is not truthy. For example:
const InputComponent = function() {
const required = true;
const disabled = false;
return (
<input type="text" disabled={disabled} required={required} />
);
}
will result in:
<input type="text" required>
Update: if anyone is curious as to how/why this happens, you can find details in ReactDOM's source code, specifically at lines 30 and 167 of the DOMProperty.js file.
juandemarco's answer is usually correct, but here is another option.
Build an object how you like:
var inputProps = {
value: 'foo',
onChange: this.handleChange
};
if (condition) {
inputProps.disabled = true;
}
Render with spread, optionally passing other props also.
<input
value="this is overridden by inputProps"
{...inputProps}
onChange={overridesInputProps}
/>
Here is an example of using Bootstrap's Button via React-Bootstrap (version 0.32.4):
var condition = true;
return (
<Button {...(condition ? {bsStyle: 'success'} : {})} />
);
Depending on the condition, either {bsStyle: 'success'} or {} will be returned. The spread operator will then spread the properties of the returned object to Button component. In the falsy case, since no properties exist on the returned object, nothing will be passed to the component.
An alternative way based on Andy Polhill's comment:
var condition = true;
return (
<Button bsStyle={condition ? 'success' : undefined} />
);
The only small difference is that in the second example the inner component <Button/>'s props object will have a key bsStyle with a value of undefined.
Here is an alternative.
var condition = true;
var props = {
value: 'foo',
...(condition && { disabled: true })
};
var component = <div {...props} />;
Or its inline version
var condition = true;
var component = (
<div value="foo" {...(condition && { disabled: true })} />
);
Here's a way I do it.
With a conditional:
<Label
{...{
text: label,
type,
...(tooltip && { tooltip }),
isRequired: required
}}
/>
I still prefer using the regular way of passing props down, because it is more readable (in my opinion) in the case of not have any conditionals.
Without a conditional:
<Label text={label} type={type} tooltip={tooltip} isRequired={required} />
Let’s say we want to add a custom property (using aria-* or data-*) if a condition is true:
{...this.props.isTrue && {'aria-name' : 'something here'}}
Let’s say we want to add a style property if a condition is true:
{...this.props.isTrue && {style : {color: 'red'}}}
You can use the same shortcut, which is used to add/remove (parts of) components ({isVisible && <SomeComponent />}).
class MyComponent extends React.Component {
render() {
return (
<div someAttribute={someCondition && someValue} />
);
}
}
If you use ECMAScript 6, you can simply write like this.
// First, create a wrap object.
const wrap = {
[variableName]: true
}
// Then, use it
<SomeComponent {...{wrap}} />
Using undefined works for most properties:
const name = "someName";
return (
<input name={name ? name : undefined} />
);
This should work, since your state will change after the Ajax call, and the parent component will re-render.
render : function () {
var item;
if (this.state.isRequired) {
item = <MyOwnInput attribute={'whatever'} />
} else {
item = <MyOwnInput />
}
return (
<div>
{item}
</div>
);
}
For some boolean attributes listed by React [1]:
<input disabled={disabled} />
// renders either `<input>` or `<input disabled>`
For other attributes:
<div aria-selected= {selected ? "" : undefined} />
// renders either `<div aria-selected></div>` or `<div></div>`
[1] The list of boolean attributes: https://github.com/facebook/react/blob/3f9480f0f5ceb5a32a3751066f0b8e9eae5f1b10/packages/react-dom/src/shared/DOMProperty.js#L318-L345
For example using property styles for custom container
const DriverSelector = props => {
const Container = props.container;
const otherProps = {
...( props.containerStyles && { style: props.containerStyles } )
};
return (
<Container {...otherProps} >
In React you can conditionally render Components, but also their attributes, like props, className, id, and more.
In React it's very good practice to use the ternary operator which can help you conditionally render Components.
An example also shows how to conditionally render Component and its style attribute.
Here is a simple example:
class App extends React.Component {
state = {
isTrue: true
};
render() {
return (
<div>
{this.state.isTrue ? (
<button style={{ color: this.state.isTrue ? "red" : "blue" }}>
I am rendered if TRUE
</button>
) : (
<button>I am rendered if FALSE</button>
)}
</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>
From my point of view the best way to manage multiple conditional props is the props object approach from #brigand. But it can be improved in order to avoid adding one if block for each conditional prop.
The ifVal helper
rename it as you like (iv, condVal, cv, _, ...)
You can define a helper function to return a value, or another, if a condition is met:
// components-helpers.js
export const ifVal = (cond, trueValue=true, falseValue=null) => {
return cond ? trueValue : falseValue
}
If cond is true (or truthy), the trueValue is returned - or true.
If cond is false (or falsy), the falseValue is returned - or null.
These defaults (true and null) are, usually the right values to allow a prop to be passed or not to a React component. You can think to this function as an "improved React ternary operator". Please improve it if you need more control over the returned values.
Let's use it with many props.
Build the (complex) props object
// your-code.js
import { ifVal } from './components-helpers.js'
// BE SURE to replace all true/false with a real condition in you code
// this is just an example
const inputProps = {
value: 'foo',
enabled: ifVal(true), // true
noProp: ifVal(false), // null - ignored by React
aProp: ifVal(true, 'my value'), // 'my value'
bProp: ifVal(false, 'the true text', 'the false text') // 'my false value',
onAction: ifVal(isGuest, handleGuest, handleUser) // it depends on isGuest value
};
<MyComponent {...inputProps} />
This approach is something similar to the popular way to conditionally manage classes using the classnames utility, but adapted to props.
Why you should use this approach
You'll have a clean and readable syntax, even with many conditional props: every new prop just add a line of code inside the object declaration.
In this way you replace the syntax noise of repeated operators (..., &&, ? :, ...), that can be very annoying when you have many props, with a plain function call.
Our top priority, as developers, is to write the most obvious code that solve a problem.
Too many times we solve problems for our ego, adding complexity where it's not required.
Our code should be straightforward, for us today, for us tomorrow and for our mates.
just because we can do something doesn't mean we should
I hope this late reply will help.
<input checked={true} type="checkbox" />
In react functional component you can try something like this to omit unnecessary tag property.
<div className="something" ref={someCondition ? dummyRef : null} />
This works for me if I need to omit tags like ref, class, etc. But I don't know if that's work for every tag property
<Button {...(isWeb3Enabled ? {} : { isExternal: true })}>
Metamask
</Button>
Given a local variable isRequired You can do the following in your render method (if using a class) or return statement (if using a function component):
<MyComponent required={isRequired ? 'true' : undefined} />
In this case, the attribute will not be added if isRequired is undefined, false, or null (which is different from adding the attribute but setting it to 'false'.) Also note that I am using strings instead of booleans in order to avoid a warning message from react (Boolean value received on non-boolean attribute).
Considering the post JSX In Depth, you can solve your problem this way:
if (isRequired) {
return (
<MyOwnInput name="test" required='required' />
);
}
return (
<MyOwnInput name="test" />
);
I think this may be useful for those who would like attribute's value to be a function:
import { RNCamera } from 'react-native-camera';
[...]
export default class MyView extends React.Component {
_myFunction = (myObject) => {
console.log(myObject.type); //
}
render() {
var scannerProps = Platform.OS === 'ios' ?
{
onBarCodeRead : this._myFunction
}
:
{
// here you can add attribute(s) for other platforms
}
return (
// it is just a part of code for MyView's layout
<RNCamera
ref={ref => { this.camera = ref; }}
style={{ flex: 1, justifyContent: 'flex-end', alignItems: 'center', }}
type={RNCamera.Constants.Type.back}
flashMode={RNCamera.Constants.FlashMode.on}
{...scannerProps}
/>
);
}
}
in an easy way
const InputText= ({required = false , disabled = false, ...props}) =>
(<input type="text" disabled={disabled} required={required} {...props} />);
and use it just like this
<InputText required disabled/>
In addition, you can make other value to Boolean
const MyComponent = function() {
const Required = "yes";
return (
<input
required={Required === "yes"}
type="text"
key={qs.Name}
name="DefaultValue"
label={qs.QuestionTitle}
onChange={(event) => handleInputChange(index, event)}
placeholder={qs.QuestionTitle}
/>
);
}
If it is for a limited number of properties this will do
function MyInput({isRequired}) {
if (isRequired) {
return <input classname="foo" isRequired={isRequired} />
}
return <input classname="foo" />
}
If you have a large number of properties, it will be difficult to write if else conditions for every property and return accordingly. For that, you can push those properties in an object and use the spread operator in the returned element.
function MyInput({ prop1, prop2, ...propN }) {
const props = {};
if (prop1) props.prop1 = prop1;
.
.
.
if (propN) props.propN = propN;
return <input classname="foo" {...props} />
}
You must set as undefined the value for when you do not need the attribute
Example:
<a data-tooltip={sidebarCollapsed?'Show details':undefined}></a>
In React, we pass values to component from parent to child as Props. If the value is false, it will not pass it as props. Also in some situation we can use ternary (conditional operator) also.
Related
So I am in a situation where I have to change a particular property from an array of objects. When the property changes I want to rerender the component. Now, this works fine without any issues when use the setPropertyName of useState. But now I am just changing one property of the object instead of the entire object.
Here is the code that Im working on:
const [movieList, setMovieList] = useState([]);
Calling the setMovieList and passing an array will obviously cause a rerender.
Consider the following contents of movieList:
movieList = [
{
'name': 'Mulholland Dr.'
'year':2001,
'watched' : true,
'rating':0
},
{
'name': 'Zodiac'
'year':2007,
'watched' : false,
'rating':0
},
{
'name': 'Twin Peaks'
'year':2017,
'watched' : true,
'rating': 0
}]
Then I have a function which renders the list:
function showMovieList () {
return movieList.map((movie) => {
return (
<List.Item key={movie.imdbID}>
<div className="watchedCheckBoxContainer">
<input type="checkbox" onChange={(event) => movie.watched = event.target.checked} id={`cb1${movie.imdbID}`}/>
<label htmlFor={`cb1${movie.imdbID}`}><Image size='tiny' src={movie.Poster} /></label>
</div>
{/* <Image size='tiny' src={movie.Poster} /> */}
<List.Content>{movie.Title}</List.Content>
{movie.watched ? <Rating maxRating={5} onRate={(event, {rating}) => movie.userRating=rating}/> : null}
</List.Item>
)
});
}
As you can see , when the checkbox is clicked it changes the value of the watched property. A few lines later I'm checking if movie.watched == true then show the <Rating> component. But in this case, I'm not using setMoviesList to update the moviesList and hence the <Rating> component is not visible.
How can I use setMoviesList to update watched property of the particular movie whose checkbox I click on?
Okay.. I solved it by the following way:
function onMovieWatched (watched, index) {
const tempMoviesList = [...movieList];
tempMoviesList[index].watched = watched;
setMovieList(tempMoviesList);
}
<input type="checkbox" onChange={(event) => onMovieWatched(event.target.checked, idx)} id={`cb1${movie.imdbID}`}/>
The idx is the index that I am using from the map method.
Initially I was afraid that I might have to loop over the entire array and get the object that matches the imdbID and then update its property.
Luckily I have the index while mapping over it, so I just used that to directly retrieve the object.
Dont know why I didnt think of this solution before posting.
ReactJS does not like null for a value in <input>.
Warning: value prop on input should not be null. Consider using an
empty string to clear the component or undefined for uncontrolled
components.
Great. My database often returns null because nothing has been set yet.
I am dynamically rendering input fields in a ReactJS app.
value={this.state[row_array[0]] || ''}
The value for this.state[row_array[0]] will sometimes be 0. I want to put 0 in the text field but this evaluates to false ... thus... the empty string '' is put in the text field.
The problem is this.state[row_array[0]] could be anything... 0 is the big problem.
This has some info but nothing helpful for me https://github.com/facebook/react/issues/11417
Any ideas about how I can set that value=0?
Use the conditional operator instead, and check for null:
value={this.state[row_array[0]] === null ? '' : this.state[row_array[0]]}
Setting the value of an input this way tells React that you want to control the component. If it's uncontrolled initially, this will result in a warning - to avoid that warning, make sure to specify that the input is controlled from the start.
Live demo:
const row_array = [0];
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = { 0: null };
this.updateData = this.updateData.bind(this);
}
updateData (event) {
console.log('updated');
this.setState({ [event.target.name]: event.target.value });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input id={row_array[0]} className="data-input" name={row_array[0]} placeholder="" value={this.state[row_array[0]] === null ? '' : this.state[row_array[0]]} onChange={this.updateData} />
</form>
);
}
}
ReactDOM.render(
<NameForm />,
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>
If it may also be undefined, and not just null, then check that as well:
value={this.state[row_array[0]] === null || this.state[row_array[0]] === undefined ? '' : this.state[row_array[0]]}
When asked for a variable, either with an if or with a ternary operator, it is evaluated to be non-null and non-undefined.
You can try.
class X extends React.Component {
state = {};
render (){
const xdata = this.state[row_array[0]];
const value = xdata ? xdata : '';
return (
<input type="text" value={value} />
)
}
}
This question may be more about opinion than fact, but I'm unsure so thought I'd ask.
I'm building some forms which will display data and allow edits, the field data comes from props (as a parent component is using a GraphQL query to pull a larger amount and pass to each child).
I'm finding some input data is evaluating to null (as it's not passed back from the query) which throws a warning as inputs don't like being assigned null values.
My question is, when passing these values, what's the cleanest way to run checks on each variable and assign an empty string if needed?
So far the two options i've tried are:
Conditionally assign each to the state object, but this feels clunky and is a lot of code:
const [state, setState] = useState({
telephone: props.telephone ? props.telephone : '',
nickname: props.nickname ? props.nickname : ''
etc...
});
Or to define a function which maps over props and checks values, before setting state:
useEffect( () => {
let state_arr = {};
Object.keys(props).map( (key) => {
if( !props[key] ) state_arr[key] = '';
else state_arr[key] = props[key];
} );
setState(state_arr);
}, [] )
Honestly this feels cleaner than the first option, but there are a number of places this will occur and to have to do this in each feels counter productive.
Any help/insight appreciated.
EDIT: It turns out OP is using Material UI for this..Meaning, the reason the input is showing a warning is due to Material UI using PropTypes. I suggested that OP create a wrapper for the <Input /> component and pass through all props. Inside of the wrapper component you can just do: <InputWrapper value={props.value || ""} {...rest} /> and this covers things..
Live Demo
InputWrapper:
import React from 'react';
import { Input } from '#material-ui/core';
export default function InputWrapper({ value, ...rest }) {
return <Input value={value || ""} {...rest} />
}
InputWrapper In Use:
import React, { useState, useEffect } from 'react';
import { render } from 'react-dom';
import InputWrapper from './InputWrapper.js';
function App(props) {
const [state, setState] = useState({});
useEffect(() => {
setState({
name: props.name,
age: props.age,
hairColor: props.hairColor,
})
}, [props.name, props.age, props.hairColor]);
const handleChange = (event, inputType) => {
setState({...state, [inputType]: event.target.value})
}
return(
<div>
{/* Shows that you can pass through native <Input /> props: */}
{/* state.name is null here! Warning is NOT thrown in the console! */}
<InputWrapper value={state.name} fullWidth onChange={e => setState({...state, name: e.target.value})} />
<InputWrapper value={state.name} multiline onChange={e => setState({...state, name: e.target.value})} />
{Object.keys(state).map((item, index) => {
return (
<div>
<InputWrapper
key={`${item}_${index}`}
value={state[item]}
onChange={e => handleChange(e, item)} />
</div>
);
})}
</div>
);
}
render(
<App name={null} age={44} hairColor="blue" />,
document.getElementById('root')
);
ORIGINAL ANSWER:
What is your use case? There is no reason to run checks and assign empty strings...
If you are trying to enforce that certain properties are used, please look into PropTypes... If you are not wanting to enforce that certain props get used, I would recommend checking for a value during use of the variable. Even if you set it to an empty string initially, you could still encounter errors down the line - I don't understand what you gain from an empty string.
I don't understand the use case - can you elaborate more on why you need to set it to an empty string?
If you really wanted to, you could verify like: useState({two: props.two || ""}) ...but it is still unnecessary..
// Notice how prop "two" is not being used..
function Test(props) {
const [state, setState] = React.useState({
one: props.one,
two: props.two
})
return(
<div>
<p>{state.one}</p>
<p>Even though <code>state.two</code> does not exist, there are no errors.. (at least for this demonstration)</p>
<input type="text" value={state.two} />
<input type="text" value={state.two} defaultValue={"default"} />
<p><i>If you really wanted to, you could verify like:</i><code>useState({two: props.two || ""})</code><i>...but it is still unnecessary..</i></p>
</div>
);
}
ReactDOM.render(<Test one="I AM ONE" />, document.body)
code {
margin: 0 10px;
padding: 3px;
color: red;
background-color: lightgray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
What about making method KickOutNullValues() which will do what you want and then you can reuse it everywhere you need. That would be more elegant.
This is a tough question, i don't know the right answer. You already tried two ways, the different way that I normally do is,
If you just want to get the display right, i would just do
<Telephone data={props.telephone} />,
const Telephone = ({data}) => { if (!data) return null }
I found this is to allow the child component to ensure the validity of this issue rather than sorting out the data in the parent API level.
Telephone.defaultProps = {
data: ''
}
This further ensures that if the data is null, it'll be reset to '' by the defaultProps
The reason I prefer this way most of time is that I don't really want to mess with the origin TRUTH of the API data.
Of course your ways might be better if you do want to ensure the data is valid at all time :)
Your code will start to have spaghetti-like qualities if you put the raw algorithm inside your callback. I recommend writing a function outside.
Your usage of Array#map is not correct, or rather you are using it in an unintended way. Array#map is used to construct an entirely new array. You are simulating Array#forEach. Also, you're performing a falsey conditional check. null is one of many values that are considered false in JavaScript. Namely, your pain points will probably be undefined, 0, and ''. If the only invalid return value is null, then check for null explicitly.
The enumerable that is for your intended use case is Array#reduce:
function nullValueReplacer(obj) {
return Object.entries(obj).reduce((newStateArr, [currentKey, currentValue]) => {
if (currentValue === null) {
newStateArr[currentKey] = ''
} else {
newStateArr[currentKey] = currentValue
}
return newStateArr
}, {});
}
As a side note, you might want to update your variable names. It's pretty deceptive that you have a variable called state_arr that is an object.
Array of objects - little fix
You should not use key with map..
think about this: (similar to yours)
useEffect(() => {
let state_arr = Object.keys(props).map(prop => prop ? {prop} : { prop: '' });
setState(state_arr);
}, [])
By using this code you make an array with object and have easy access for every item
In case there is no nickname it will look like that:
[{ telephone: '245-4225-288' }, { nickname: '' }]
What do you think?
i have a conundrum.
I have text I want toggling in a component.
basically true => tick and false => untick
but i want a 3rd scenario where I don't want any text displayed
if I don't pass the prop to that component, it is automatically assuming true. can I pass propName={null} or something like that?
or will i have to extract this into a function (i'd much rather not)
If you do not provide the prop to the component it will not be present in the object representing props within that component. You could therefore check whether or not the prop is defined:
const Component = (props) => {
if (props.checked === undefined) {
return <Something />;
}
return <SomethingElse someProp={props.checked} />;
};
Then, this will render Something:
<Component />
And these will all render SomethingElse:
<Component checked />
<Component checked={true} />
<Component checked={false} />
You can have a third state null but then you will need to do explicit comparisons i.e a == false and don't evaluate a as it will become false in case of null as well
So, React newbie here... I'll start off by saying I have a simple single page application which consists of a few simple pages.
Using react-router I have a 'top-down' set up for my components. To give you a basic idea of my SPA structure see below:
index -- layout(react routers) --
|--About Page
|--Home Page
|--Contact Page
I am rendering a component called "GlobalHero" from my Home Page component.
Here is the GlobalHero.jsx component.
import React from "react";
var classNames = require('classnames');
import s from '../../../index.scss';
class GlobalHero extends React.Component {
constructor() {
super();
//sets initial state
this.state = {
fadeIn: "",
titleSelected: "",
subTitleSelected: ""
};
}
// <<========= ON COMPONENT RENDER =========
componentDidMount = () => {
console.log("GlobalHero");
console.log(this.props);
this.handleClass("fadeIn");
}
// =========>>
// <<========= CONTROLS THE STATE =========
handleClass = (param) => {
if (param === "fadeIn" && this.state.fadeIn != "true") {
this.setState({fadeIn: "true"});
}
if (param === "titleSelected" && this.state.titleSelected != "true") {
this.setState({titleSelected: "true"});
}
if (param === "subTitleSelected" && this.state.subTitleSelected != "true") {
this.setState({subTitleSelected: "true"});
}
}
// =========>>
render() {
const heroImg = require(`../../../images/hero${this.props.page}.jpg`);
//REMOVES CLASS IN REALTIME BASED ON STATE'S VALUE =========
var containerClasses = classNames({
[s['text-center']]: true,
[s["hidden"]]: this.state.fadeIn != "true",
[s["fadeIn"]]: this.state.fadeIn === "true"
});
var titleClasses = classNames({
[s['blue']]: this.state.titleSelected === "true"
});
var subTitleClasses = classNames({
[s['subTitle']]: true,
[s['text-center']]: true,
[s['blue']]: this.state.subTitleSelected === "true"
});
// =========>>
return (
<div className={s["container-fluid"]}>
<div className={s["row"]}>
<div className={s["col-lg-16"]}>
<div className={containerClasses}>
<img src={heroImg} className={s["hero__img"]}></img>
<h1 onClick={() => this.handleClass("titleSelected")} className={titleClasses}>{this.props.page}!</h1>
<p className={subTitleClasses} onClick={() => this.handleClass("subTitleSelected")}>{this.props.name}, {this.props.age}, {this.props.city}</p>
</div>
</div>
</div>
</div>
);
}
}
export default GlobalHero;
I noticed there is a lot of complexity there for assigning a few simple class names to the component's elements.
I was wondering if there is a better practice for doing this? Maybe
using an external js page to manage my classnames?
Any input or adivce is appreciated... Thankyou in adnvance.
Your title mentions BEM but it looks like you are using CSS Modules, which is inspired by similar ideas but not the same thing.
Anyway, this is quite subjective but I have a few thoughts that are too much to fit in a comment:
Assuming you are using css modules through Webpack's css-loader, you can use camelCase to make your style properties more JS friendly:
loader: "css-loader?modules&camelCase"
Now for .text-center css class name you can simply use s.textCenter instead of s["test-center"].
You could componentize this better: first, you are kind of doing a lot for a single component, but you could break it down into a few smaller components that each have a single responsibility (for example container, title, subtitle). Second, your handleClass() method is doing a lot, when you could just have simple handlers that call setState() without knowing anything about class names. In other words, the component should have props and state, only the render() function deals with how to translate that into class names to render. You also really don't need to check the state's current value before setting it. Just set it to what it should be and let React optimize rendering performance for you.
You have boolean state flags that you store using strings "true" and "false"... this makes it noisy to handle, just store as booleans.
You have a lot of [s["class-name"]]: true which is not necessary; if you always want a class name to be rendered just pass it as an argument to classNames:
classNames(s.subTitle, { [s.blue]: this.state.subTitleSelected })
There's no reason to call a handler on componentDidMount, just initialize the state how you want it.
It looks like you're using bootstrap CSS but not the React Bootstrap components. I would highly recommend using React Bootstrap.
Putting that together I'd have something like:
class GlobalHero extends React.Component {
state = {
fadeIn: true,
titleSelected: false,
subTitleSelected: false
};
handleTitleClick = () => {
this.setState({titleSelected: true});
};
handleSubTitleClick = () => {
this.setState({subTitleSelected: true});
};
render() {
return (
<Grid fluid>
<Row>
<Col lg={16}>
<HeroContainer fadeIn={this.state.fadeIn}>
<HeroImage page={this.props.page} />
<HeroTitle selected={this.state.titleSelected}
onClick={this.handleTitleClick}
page={this.props.page} />
<HeroSubTitle selected={this.state.subTitleSelected}
onClick={this.handleSubTitleClick}
name={this.props.name}
age={this.props.age}
city={this.props.city} />
</HeroContainer>
</Col>
</Row>
</Grid>
);
}
}
const HeroContainer = ({fadeIn, children}) => {
return (
<div className={classNames(s.textCenter, fadeIn ? s.fadeIn : s.hidden)}>
{children}
</div>
);
};
const HeroImage = ({page}) => {
const heroImg = require(`../../../images/hero${page}.jpg`);
return (
<img src={heroImg} className={s.heroImg} />
);
};
const HeroTitle = ({onClick, selected, page}) => (
<h1 onClick={onClick} className={selected ? s.blue : null}>{page}!</h1>
);
const HeroSubTitle = ({onClick, selected, name, age, city}) => (
<p className={classNames(s.subTitle, s.textCenter, { [s.blue]: selected })} onClick={onClick}>
{name}, {age}, {city}
</p>
);
Breaking it into smaller components like this is not completely necessary, but notice how from the perspective of GlobalHero it does nothing with styles, it just sets props and state, and the little parts have no state, they just render the correct styles based on props.
PS maybe this should move to Code Reviews?