I'm working on a HTMl5 video player for a French company. We use React and Redux to build the UI, and it works very well, it's very pleasant to code ! We currently use eslint-plugin-react to check React code style. Since last versions, the linter recommands to use pure functions instead of React Components (view the rule) but it raises some debates in my team.
We are already using pure function for very small components that render always the same things (for given props). No problems with that. But for bigger components, in my opinion, pure functions seem to make the code less elegant (and less uniform compared to other components).
This is an example of one of our components that should be changed :
const ControlBar = ({ actions, core, root }) => {
const onFullscreenScreen = (isFullscreen) => {
const playerRoot = root;
if (isFullscreen && !screenfull.isFullscreen) {
screenfull.request(playerRoot);
} else {
screenfull.exit();
}
};
const renderIconButton = (glyph, action, label = false) => {
let content = (
<Button modifier="icon" clickCallback={ action }>
<Icon glyph={ glyph } />
</Button>
);
if (label) {
content = <TooltipOrigin content={label}>{content}</TooltipOrigin>;
}
return content;
};
const renderPlayButton = () => {
const { play, pause } = actions;
const { playerState } = core;
if (playerState === CoreStates.PAUSED) {
return renderIconButton(playGlyph, play, 'lecture');
}
return renderIconButton(pauseGlyph, pause, 'pause');
};
const renderMuteButton = () => {
const { mute, unmute } = actions;
const { currentVolume } = core;
if (currentVolume === 0) {
return renderIconButton(muteGlyph, unmute);
}
return renderIconButton(volumeGlyph, mute);
};
const renderFullscreenButton = () => {
const { isFullscreen } = core;
if (!isFullscreen) {
return renderIconButton(fullscreenGlyph, () => { onFullscreenScreen(true); });
}
return renderIconButton(fullscreenExitGlyph, () => { onFullscreenScreen(false); });
};
const { setCurrentVolume } = actions;
const { currentVolume } = core;
return (
<div className={ style.ControlBar }>
<div className={ style.audio }>
{ renderMuteButton() }
<SoundBar setCurrentVolume={ setCurrentVolume } volume={ currentVolume } />
</div>
<div className={ style.controls }>
{ renderPlayButton() }
</div>
<div className={ style.settings }>
{ renderFullscreenButton() }
</div>
</div>
);
};
ControlBar.propTypes = {
actions: PropTypes.object.isRequired,
core: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
};
export default ControlBar;
versus :
export default class ControlBar extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
core: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
};
onFullscreenScreen(isFullscreen) {
const playerRoot = this.props.root;
if (isFullscreen && !screenfull.isFullscreen) {
screenfull.request(playerRoot);
} else {
screenfull.exit();
}
}
renderIconButton(glyph, action, label = false) {
let content = (
<Button modifier="icon" clickCallback={ action }>
<Icon glyph={ glyph } />
</Button>
);
if (label) {
content = <TooltipOrigin content={label}>{content}</TooltipOrigin>;
}
return content;
}
renderPlayButton() {
const { play, pause } = this.props.actions;
const { playerState } = this.props.core;
if (playerState === CoreStates.PAUSED) {
return this.renderIconButton(playGlyph, play, 'lecture');
}
return this.renderIconButton(pauseGlyph, pause, 'pause');
}
renderMuteButton() {
const { mute, unmute } = this.props.actions;
const { currentVolume } = this.props.core;
if (currentVolume === 0) {
return this.renderIconButton(muteGlyph, unmute);
}
return this.renderIconButton(volumeGlyph, mute);
}
renderFullscreenButton() {
const { isFullscreen } = this.props.core;
if (!isFullscreen) {
return this.renderIconButton(fullscreenGlyph, () => { this.onFullscreenScreen(true); });
}
return this.renderIconButton(fullscreenExitGlyph, () => { this.onFullscreenScreen(false); });
}
render() {
const { setCurrentVolume } = this.props.actions;
const { currentVolume } = this.props.core;
return (
<div className={ style.ControlBar }>
<div className={ style.audio }>
{ this.renderMuteButton() }
<SoundBar setCurrentVolume={ setCurrentVolume } volume={ currentVolume } />
</div>
<div className={ style.controls }>
{ this.renderPlayButton() }
</div>
<div className={ style.settings }>
{ this.renderFullscreenButton() }
</div>
</div>
);
}
}
We like the structure of a React Component. The PropTypes and default props can be inside the class thanks to ES7, it's not possible with pure functions. And, in this example particularly, we have many function to render sub-components.
We could simply disable this rule if we don't like, but we really want to understand that and we care about performance and React good practices. So, maybe you can help us.
How can you help me ? I would get other opinions about this interesting problematic. What are the arguments in favor of pure functions ?
Maybe the solution is not to change ControlBar Component in pure function but just to improve it. In this case, what would be your advices to do that ?
Thanks a lot for your help !
Related
How can I move one or more const out of function App?
In the simple test App below, I'm using localStorage to store a value which determines if a div is dispayed. The handleToggle dismisses the div and stores a value in localStorage. Clearing localstorage and reloading shows the div again.
In a simple test App on localhost, this works. But in my more complex production App, I'm getting the error Invalid hook call. Hooks can only be called inside of the body of a function component , which has a myriad of fixes, one of which points out the issue may be that a const needs to be a separate function.
And so I'm thinking the issue is that I need to convert the two const to a function that can be placed right under the import blocks and out of the function App() block.
As a start, in this simple App, how can I move the two const out of function App()?
import './App.css';
import * as React from 'react';
function App() {
const [isOpen, setOpen] = React.useState(
JSON.parse(localStorage.getItem('is-open')) || false
);
const handleToggle = () => {
localStorage.setItem('is-open', JSON.stringify(!isOpen));
setOpen(!isOpen);
};
return (
<div className="App">
<header className="App-header">
<div>{!isOpen && <div>Content <button onClick={handleToggle}>Toggle</button></div>}</div>
</header>
</div>
);
}
export default App;
Edit: This is the full production file with Reza Zare's fix that now throws the error 'import' and 'export' may only appear at the top level on line 65:
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
import {
Compose,
Notifications,
HomeTimeline,
CommunityTimeline,
PublicTimeline,
HashtagTimeline,
DirectTimeline,
FavouritedStatuses,
BookmarkedStatuses,
ListTimeline,
Directory,
} from '../../ui/util/async-components';
import ComposePanel from './compose_panel';
import NavigationPanel from './navigation_panel';
import { supportsPassiveEvents } from 'detect-passive-events';
import { scrollRight } from '../../../scroll';
const componentMap = {
'COMPOSE': Compose,
'HOME': HomeTimeline,
'NOTIFICATIONS': Notifications,
'PUBLIC': PublicTimeline,
'REMOTE': PublicTimeline,
'COMMUNITY': CommunityTimeline,
'HASHTAG': HashtagTimeline,
'DIRECT': DirectTimeline,
'FAVOURITES': FavouritedStatuses,
'BOOKMARKS': BookmarkedStatuses,
'LIST': ListTimeline,
'DIRECTORY': Directory,
};
// Added const
const getInitialIsOpen = () => JSON.parse(localStorage.getItem('is-open')) || false;
const App = () => {
const [isOpen, setOpen] = React.useState(getInitialIsOpen());
const handleToggle = () => {
localStorage.setItem('is-open', JSON.stringify(!isOpen));
setOpen(!isOpen);
};
function getWeekNumber(d) {
d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay()||7));
var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
var weekNo = Math.ceil(( ( (d - yearStart) / 86400000) + 1)/7);
return [d.getUTCFullYear(), weekNo];
}
var result = getWeekNumber(new Date());
// errors out here: 'import' and 'export' may only appear at the top level.
export default class ColumnsArea extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
columns: ImmutablePropTypes.list.isRequired,
isModalOpen: PropTypes.bool.isRequired,
singleColumn: PropTypes.bool,
children: PropTypes.node,
};
// Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS
mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)');
state = {
renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches),
}
componentDidMount() {
if (!this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
}
if (this.mediaQuery) {
if (this.mediaQuery.addEventListener) {
this.mediaQuery.addEventListener('change', this.handleLayoutChange);
} else {
this.mediaQuery.addListener(this.handleLayoutChange);
}
this.setState({ renderComposePanel: !this.mediaQuery.matches });
}
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
}
componentWillUpdate(nextProps) {
if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
this.node.removeEventListener('wheel', this.handleWheel);
}
}
componentDidUpdate(prevProps) {
if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) {
this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false);
}
}
componentWillUnmount () {
if (!this.props.singleColumn) {
this.node.removeEventListener('wheel', this.handleWheel);
}
if (this.mediaQuery) {
if (this.mediaQuery.removeEventListener) {
this.mediaQuery.removeEventListener('change', this.handleLayoutChange);
} else {
this.mediaQuery.removeListener(this.handleLayouteChange);
}
}
}
handleChildrenContentChange() {
if (!this.props.singleColumn) {
const modifier = this.isRtlLayout ? -1 : 1;
this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
}
}
handleLayoutChange = (e) => {
this.setState({ renderComposePanel: !e.matches });
}
handleWheel = () => {
if (typeof this._interruptScrollAnimation !== 'function') {
return;
}
this._interruptScrollAnimation();
}
setRef = (node) => {
this.node = node;
}
renderLoading = columnId => () => {
return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading multiColumn />;
}
renderError = (props) => {
return <BundleColumnError multiColumn errorType='network' {...props} />;
}
render () {
const { columns, children, singleColumn, isModalOpen } = this.props;
const { renderComposePanel } = this.state;
if (singleColumn) {
return (
<div className='columns-area__panels'>
<div className='columns-area__panels__pane columns-area__panels__pane--compositional'>
<div className='columns-area__panels__pane__inner'>
{renderComposePanel && <ComposePanel />}
</div>
</div>
<div className='columns-area__panels__main'>
<div className='tabs-bar__wrapper'><div id='tabs-bar__portal' />
// output of getInitialIsOpen
<div class='banner'>
{!isOpen && <div>Content <button onClick={handleToggle}>Toggle</button></div>}
</div>
</div>
<div className='columns-area columns-area--mobile'>{children}</div>
</div>
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
<div className='columns-area__panels__pane__inner'>
<NavigationPanel />
</div>
</div>
</div>
);
}
return (
<div className={`columns-area ${ isModalOpen ? 'unscrollable' : '' }`} ref={this.setRef}>
{columns.map(column => {
const params = column.get('params', null) === null ? null : column.get('params').toJS();
const other = params && params.other ? params.other : {};
return (
<BundleContainer key={column.get('uuid')} fetchComponent={componentMap[column.get('id')]} loading={this.renderLoading(column.get('id'))} error={this.renderError}>
{SpecificComponent => <SpecificComponent columnId={column.get('uuid')} params={params} multiColumn {...other} />}
</BundleContainer>
);
})}
{React.Children.map(children, child => React.cloneElement(child, { multiColumn: true }))}
</div>
);
}
}
I'm kind of new w/ react and nextjs. How can insert a script in a component to add class in when the page reload? It seems like the code below don't work because the page is not yet rendered when I add the class for the body tag.
const ModeToggler = (props: Props) => {
// ** Props
const { settings, saveSettings } = props
const handleModeChange = (mode: PaletteMode) => {
saveSettings({ ...settings, mode })
}
const handleModeToggle = () => {
if (settings.mode === 'light') {
handleModeChange('dark');
document.body.classList.add('mode-dark');
} else {
handleModeChange('light');
document.body.classList.remove('mode-dark');
}
}
// This will not work because the page is not rendered yet right?
if (settings.mode === 'light') {
document.body.classList.add('mode-dark');
} else {
document.body.classList.remove('mode-dark');
}
return (
<IconButton color='inherit' aria-haspopup='true' onClick={handleModeToggle}>
{settings.mode === 'dark' ? <WeatherSunny /> : <WeatherNight />}
</IconButton>
)
}
export default ModeToggler
In your case, since the class is dependent on the prop settings
I would suggest you use a useEffect with a dependency of that prop. So not only will it retrieve the value and apply the style on render, but also re-render/apply style each time the prop changes.
const ModeToggler = (props: Props) => {
// ** Props
const { settings, saveSettings } = props
const handleModeChange = (mode: PaletteMode) => {
saveSettings({ ...settings, mode })
}
const handleModeToggle = () => {
if (settings.mode === 'light') {
handleModeChange('dark');
document.body.classList.add('mode-dark');
} else {
handleModeChange('light');
document.body.classList.remove('mode-dark');
}
}
useEffect(() => {
if (settings.mode === 'light') {
document.body.classList.add('mode-dark');
} else {
document.body.classList.remove('mode-dark');
}
}, [settings.mode])
return (
<IconButton color='inherit' aria-haspopup='true' onClick={handleModeToggle}>
{settings.mode === 'dark' ? <WeatherSunny /> : <WeatherNight />}
</IconButton>
)
}
export default ModeToggler
you can detect if your page is reload or not using window.performance
for more info https://developer.mozilla.org/en-US/docs/Web/API/Performance/getEntriesByType
useEffect(()=>{
let entries = window.performance.getEntriesByType("navigation");
if(entries[0]=="reload"){
// add class to an element;
}
},[])
I have been working on a React project for some time now. I have a board with elements on it. These elements contain images. I have a working version of it here: https://geoboard.luukwuijster.dev/ . This version is just in plain JavaScript and jQuery, but it does illustrate what I want to achieve in the new React version. When I upload an image (by pasting it using CTRL + V) I want it to be animated. It should slide in and slide back out. In the older version I am just using jQuery's slideIn and slideOut, but I am now wondering how I can achive this in React.
I have already tried the following things:
CSSTransition. I have tried using CSSTransition by setting the height of the element to zero and having a transition on height. This does not work because the height of the element is variable, so it should be auto. transition does not work on height: auto.
Animate.CSS I have tried putting a slideInDown on the element, but since Animcate.CSS is using Transform, this isn't working. The Element just pops in a couple pixels above and slides down.
jQuery's slideIn and slideOut. This is somewhat working, but just bugs out. I use ref to get the element, but when adding a new element to the array it animates the last element and not the first. It's also glitching a bit when animating.
This is my code:
Board.tsx
import React from 'react';
import { Config } from 'util/config';
import { container } from 'tsyringe';
import { AppState } from 'store';
import { connect } from 'react-redux';
import { BoardState } from 'store/board/types';
import { BoardHubService } from 'services/hubs/boardHub.service';
import { BoardElementViewModel } from 'models/BoardElementViewModel';
import { BoardViewModel } from 'models/BoardViewModel';
import { BoardElement } from './boardElement/boardElement';
import { HttpService } from 'services/http.service';
import { setActiveBoard } from 'store/board/actions';
import './board.scss'
import { mapToType } from 'helpers/helpers';
interface BoardProps {
activeBoardState: BoardState;
setActiveBoard: typeof setActiveBoard;
}
interface LocalBoardState {
boardElements: Array<BoardElementViewModel>
}
class Board extends React.Component<BoardProps, LocalBoardState> {
private config: Config;
private httpService: HttpService;
private boardHubService: BoardHubService;
constructor(props: any) {
super(props);
this.config = container.resolve(Config);
this.boardHubService = container.resolve(BoardHubService);
this.httpService = container.resolve(HttpService);
this.state = {
boardElements: []
}
}
async componentDidMount() {
// If there was any active board on page load...
if (this.props.activeBoardState.boardId) {
await this.loadBoardElements();
}
this.boardHubService.getConnection().on('SwitchedBoard', (response: BoardViewModel) => {
this.setState({
boardElements: response.elements
});
console.log('SwitchedBoard', this.props.activeBoardState);
this.updateSiteTitle();
});
this.boardHubService.getConnection().on('ReceiveElement', (response: BoardElementViewModel) => {
let elements = this.state.boardElements;
elements.unshift(response);
this.setState(() => ({
boardElements: elements
}))
});
this.boardHubService.getConnection().on('RemoveElement', (response: string) => {
let elements = this.state.boardElements;
let element = mapToType<BoardElementViewModel>(elements.find(x => x.id === response));
elements.splice(elements.indexOf(element), 1);
this.setState(() => ({
boardElements: elements
}))
});
}
/**
* Load the elements from the board that was already active on page load.
*/
private async loadBoardElements() {
await this.httpService.getWithAuthorization<Array<BoardElementViewModel>>(`/boards/${this.props.activeBoardState.boardId}/elements`)
.then((response: Array<BoardElementViewModel>) => {
this.setState({
boardElements: response
});
})
.catch((e) => console.warn(e));
}
private updateSiteTitle() {
if (this.props.activeBoardState.boardId != null) {
document.title = `${this.props.activeBoardState.name} | ${this.config.siteName}`;
}
else {
document.title = this.config.siteName;
}
}
render() {
return (
<>
{this.props.activeBoardState.boardId != null
?
<div className="board-elements">
{this.state.boardElements.map((element: BoardElementViewModel, index) => {
return (
<BoardElement
key={index}
id={element.id}
// TODO: Use number from server
number={element.elementNumber}
user={element.user}
direction={element.direction}
note={element.note}
imageId={element.imageId}
createdAt={element.createdAt}
/>
)
})}
</div>
:
<div className="select-board-instruction">
<h1>Please select or create a board.</h1>
</div>
}
</>
)
}
}
const mapStateToProps = (state: AppState) => ({
activeBoardState: state.activeBoard
});
export default connect(mapStateToProps, { setActiveBoard })(Board);
BoardElement.tsx
import React from 'react';
import { UserViewModel } from 'models/UserViewModel';
import { Direction } from 'models/Direction';
import './boardElement.scss';
import { dateToReadableString } from 'helpers/helpers';
import { Config } from 'util/config';
import { container } from 'tsyringe';
import { HttpService } from 'services/http.service';
import { CSSTransition, Transition } from 'react-transition-group';
import $ from 'jquery'
interface BoardElementProps {
id: string;
number: number;
user: UserViewModel;
// TODO: Use direction Enum
direction?: Direction;
note?: string;
imageId?: string;
createdAt: Date;
}
export class BoardElement extends React.Component<BoardElementProps> {
private config: Config;
private httpService: HttpService;
private ref: any;
constructor(props: BoardElementProps) {
super(props);
this.state = {
show: false,
height: 0
}
this.config = container.resolve(Config);
this.httpService = container.resolve(HttpService);
}
getReadableDirection(direction: Direction) {
// TODO: Richtingen vertalen
switch (direction) {
case Direction.North: return 'Noord';
case Direction.NorthEast: return 'Noordoost';
case Direction.East: return 'Oost';
case Direction.SouthEast: return 'Zuidoost';
case Direction.South: return 'Zuid';
case Direction.SouthWest: return 'Zuidwest';
case Direction.West: return 'West';
case Direction.NorthWest: return 'Noordwest';
}
}
removeElement() {
$(this.ref).slideUp(200);
setTimeout(() => {
this.httpService.deleteWithAuthorization(`/boards/elements/${this.props.id}`).then(() => {
}, (error) => {
console.warn(error);
});
}, 250);
}
componentDidMount() {
setTimeout(() => {
$(this.ref).slideDown(200);
}, 500);
}
render() {
let style: any = { display: "none" };
return (
<div ref={element => this.ref = element} style={style}>
<div className="board-element" data-element-id={this.props.id}>
<div className="board-element-header">
<span className="board-element-number">{this.props.number}</span>
<span className="board-element-creator">{this.props.user.username}</span>
<i className="fas fa-trash ml-auto delete-icon" onClick={() => this.removeElement()}></i>
</div>
<div className="board-element-body">
{this.props.imageId
? <img className="board-element-image" src={`${this.config.apiUrl}/content/${this.props.imageId}`} />
: <p className="board-element-message">{this.props.note}</p>
}
</div>
<div className="board-element-footer">
{this.props.direction &&
<div className="board-element-direction">
<i className="fas fa-location-arrow direction mr-2"></i>{this.getReadableDirection(this.props.direction)}
</div>
}
<time className="board-element-timestamp" dateTime={this.props.createdAt.toString()}>{dateToReadableString(this.props.createdAt)}</time>
</div>
</div>
</div>
)
}
}
UPDATE:
https://gyazo.com/6dfb3db8cbd29da0f4f52af33c545421
This clip might illustrate my problem better. Look closly at the number in the corner of the elements. When I paste a new image the animation is not applied to the new image, but rather the last image in the array.
In my TextInput field I called onChange action if text has been changed. It works very fine with redux.
But i need to add more actions:
onFocuse (if text payload === '0', then i need to change TextInput value to '')
onBlur (if text payload === '', then i need to change TextInput value to '0')
I don't have any idea. For example JQuery decision:
function addEventOnChange(obj){
jQuery('#'+obj).bind('keyup mouseup change',function(e){
change(document.getElementById(obj));
});
jQuery('#'+obj).click(
function(){//focusin
clears(document.getElementById(obj));
});
jQuery('#'+obj).blur(
function(){//focusout
backzero(document.getElementById(obj));
});
function clears(obj) {
if (obj.value == 0) {
obj.value = '';
}
}
function backzero(obj) {
if (obj.value == "") {
obj.value = 0;
}
}
My current action:
export const textInputChanged = (text) => {
return {
type: TEXTINPUT_CHANGED,
payload: text
};
};
Current reducer:
const INITIAL_STATE = {
textinput: '1000'
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case TEXTINPUT_CHANGED:
return { ...state, textinput: action.payload };
default:
return state;
}
};
Current App:
onTextInputChange(text) {
this.props.TextInputChanged(number(text));
}
render() {
const number = (text) => { // only for numbers input
if (text.match(',') !== null) {
text = text.replace(',', '.');
}
if (text.match(/[*.*][0-9]*[*.*]/) !== null) {
if (text.match(/\.$/)) {
text = text.replace(/\.$/, '');
} else {
text = text.replace(/[.]/, '');
}
}
return text.replace(/[^\d.]/g, '');
};
return (
<Card>
<TextInput
value={this.props.texinput}
onChangeText={this.onTextInputChange.bind(this)}
onFocus={ clears }
onBlur={ backzero }
/>
</Card>
const mapStateToProps = (state) => {
return {
textinput: state.form.textinput,
};
};
export default connect(mapStateToProps, {
TextInputChanged
})(App);
Decision smells like componentDidMount(), but I don't feel as well
It's working. Somewhere may be a more elegant solution. You can add TextInput2, TextInput3, etc. by the same principle in this onFocus && onBlur functions. It turned out without eval)
Current App:
class App extends Component {
constructor() {
super();
this.state = {
TextInputColor: '#525050'
};
}
onFocus(input, text) {
this.setState({
[`${input}Color`]: '#000000'
});
if (text === '0') {
this.props[`${input}Changed`]('');
}
}
onBlur(input, text) {
this.setState({
[`${input}Color`]: '#525050'
});
if (text === '') {
this.props[`${input}Changed`]('0');
}
}
onTextInputChange(text) {
this.props.TextInputChanged(number(text));
}
render() {
const number = (text) => { // only for numbers input };
return (
<Card>
<TextInput
value={this.props.texinput}
onChangeText={this.onTextInputChange.bind(this)}
onBlur={() => this.onBlur('TextInput', this.props.textinput)}
onFocus={() => this.onFocus('TextInput',this.props.textinput)}
style={{ color: this.state.TextInputColor }}
/>
</Card>
const mapStateToProps = (state) => {
return {
textinput: state.form.textinput,
};
};
export default connect(mapStateToProps, {
TextInputChanged
})(App);
Gif
I think this is what you're looking for. It was also the first result from google. Just an FYI..
Focus style for TextInput in react-native
Update....
<TextInput
onBlur={ () => this.onBlur() }
onFocus={ () => this.onFocus() }
style={{ height:60, backgroundColor: this.state.backgroundColor, color: this.state.color }} />
onFocus() {
this.setState({
backgroundColor: 'green'
})
},
onBlur() {
this.setState({
backgroundColor: '#ededed'
})
},
I can't write all the code for you... You will need to setup your code.
handleChange(e){
//check if text is what you need then call some function
//e === 0 ? this.onBlur (or this.setState({...})) : null
}
class Child extends Component {
componentWillAppear(callback) {
console.log('will appear');
callback();
}
componentDidAppear() {
console.log('did appear');
}
componentWillEnter(callback) {
callback();
console.log('will enter');
}
componentDidEnter() {
console.log('did enter');
}
componentWillLeave(callback) {
callback();
console.log('wiil leave');
}
componentDidLeave() {
console.log('did leave');
}
componentWillUnmount() {
console.log('will unmount');
}
render() {
return (
<div key={this.props.children} className="item">{this.props.children}</div>
);
}
}
class Carousel extends Component {
state = {
idx: 0,
items: ['abc', 'def', 'hij']
};
onClick = ()=> {
var idx = this.state.idx + 1;
this.setState({
idx: idx
});
};
getChild(item) {
return <Child>{item}</Child>;
}
render() {
const idx = this.state.idx;
const items = this.state.items;
const item = items[idx];
const child = this.getChild(item);
return (
<div>
<ReactTransitionGroup
component="div"
className="carousel"
transitionName="item"
>
{child}
</ReactTransitionGroup>
<div onClick={this.onClick} className="btn">click me</div>
</div>
);
}
}
At first, i want to make a carousel by ReactCSSTransitionGroup, but there is a simple css transition. If i want to add some events, it could not work.
So, i want to use ReactTransitionGroup. But once i use it, i do not get i want for some hook do not work.
Not all ReactTransitionGroup lifecycle hooks fire! In the above examples, only
'will appear'
'did appear'
logs.
If there is a bug in React?