Controlled TextField loosing focus after every character from keyboard - reactjs

Whenever I input a character into the field the focus goes away.
How do I correct that?
"use strict"
import React from "react";
import createReactClass from "create-react-class";
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import {Card, CardActions, CardHeader, CardMedia, CardTitle, CardText} from 'material-ui/Card';
export var Config = createReactClass({
setConfig: function() {
localStorage.serverUrl=this.owner.state.serverUrl;
location.reload();
},
setUrl: function(evt) {
this.owner.setState({serverUrl: evt.target.value});
},
render: function() {
var {owner}=this.props;
this.owner=owner;
return <div>
<CardTitle title={__("Server")} />
<CardText>
<TextField id="serverUrl" floatingLabelText={__("Server URL")} value={owner.state.serverUrl} onChange={this.setUrl} />
</CardText>
<CardActions>
<RaisedButton label={__("Apply")} onTouchTap={this.setConfig} />
</CardActions>
</div>;
}
});
There are of course other elements on the root of the application (the actual logic is a bit more complicated, here only the structure):
<MuiThemeProvider muiTheme={muiTheme}>
<div>
<AppBar title={__("Demo")}
iconElementRight={right}
onRightIconButtonClick={this.logon}
onLeftIconButtonClick={this.toggleMenu} />
<Drawer open={this.state.menuOpen}
docked={false}
onLeftIconButtonTouchTap={this.toggleMenu}
onTouchTap={this.toggleMenu}>
<RaisedButton onTouchTap={this.toggleMenu} label={__("Menu")} />
<Menu>
<MenuEntry owner={this} value="/config" title={__("Server")} />
<MenuEntry owner={this} value="/logon" title={__("Logon")} />
<Divider />
<MenuEntry owner={this} value="/about" title={__("About")} />
</Menu>
</Drawer>
<Paper>
<Config owner={this} />
</Paper>
</div>
</MuiThemeProvider>;
Here the implementation MenuEntry
"use strict";
import React from "react";
import createReactClass from "create-react-class";
import MenuItem from 'material-ui/MenuItem';
export var MenuEntry=createReactClass({
onChange: function() {
this.owner.setState({menuOpen: false, systemMenuOpen: false, location: this.value});
},
render: function() {
var {title, owner, value, color}=this.props;
this.owner=owner;
this.value=value;
var selected=(owner.state.location==value);
return <MenuItem checked={selected} onTouchTap={this.onChange} backgroundcolor={color}>
{title}
</MenuItem>;
}
});
Versions:
Material-UI: 0.20
React: 16.2.0
Browser: Chrome 63.0.3239.123 (Mac & Windows & Android)
FF 57.0.4 (Mac & Windows)

As mentionned in material-ui docs about the onChange prop of <TextField> :
Signature: function(event: object, newValue: string) => void
event: Change event targeting the text field.
newValue: The new value of the text field.
So i think you should remove setUrl method and change <TextField> like this (no need to use onBlur) :
<TextField id="serverUrl" floatingLabelText={__("Server URL")} value={owner.state.serverUrl} onChange={(evt, value) => this.owner.setState({ serverUrl: value })} />
More comments about the code :
I'm maybe wrong but calling the setState method of a parent component passed in props seems a bad practice to me, you should consider handling the state of the input in the state of Config component.
Also, you're using create-react-class instead of a class component, it seems useless to use it in your case (you're using es6 import while create-react-class is used to build react app without es6) see official docs for more infos.
Edit : I'm able to reproduce a minimal working example :
Config.js :
import React from 'react';
import createReactClass from 'create-react-class';
import TextField from 'material-ui/TextField';
export var Config = createReactClass({
setUrl: function(evt, value) {
this.owner.setState({ serverUrl: value });
},
render: function() {
var { owner } = this.props;
this.owner = owner;
return (
<div>
<TextField
id="serverUrl"
floatingLabelText={'Server URL'}
value={owner.state.serverUrl}
onChange={this.setUrl}
/>
</div>
);
},
});
index.js :
import React, { Component } from 'react';
import createReactClass from 'create-react-class';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { render } from 'react-dom';
import { Config } from './Config';
var App = createReactClass({
getInitialState: function() {
return { serverUrl: 'test' };
},
render: function() {
console.log(this.state.serverUrl);
return (
<MuiThemeProvider>
<Config owner={this} />
</MuiThemeProvider>
);
},
});
render(<App />, document.querySelector('#root'));

I guess the issue is with Textfield value props and onBlur. The state value that you are passing to value prop is incorrect i.e., value={owner.state.serverUrl} it should be value={this.owner.state.serverUrl}.
The onBlur fuction doesn't exist in your code but you trying to call onBlur={this.onBlur} which should be removed from TextField component. It seems onBlur is not a valid prop to TextField as per material UI docs
<TextField id="serverUrl" floatingLabelText={__("Server URL")} value={this.owner.state.serverUrl} onChange={(evt, value) => this.owner.setState({ serverUrl: value })} />

Related

'X' is declared but its value is never read. Having trouble understanding where to assign it

Button.tsx
import React from "react";
interface ButtonProps {
name: string;
onButtonClick: () => void;
}
function Button(props: ButtonProps) {
const { name, onButtonClick } = props;
function buttonAlert() {
alert(`Testing`);
}
return (
<div>
<button className="buttonOne" onClick={buttonAlert}>{name}</button>
</div>
);
}
export default Button;
index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Button from './Button';
ReactDOM.render(
<React.StrictMode>
<App />
<Button name="Button One" onButtonClick={() => { alert(`You clicked on Button One! 👆🏻`); }} />
<Button name="Button Two" onButtonClick={() => { alert(`You clicked on Button Two! 👆🏻`); }} />
<Button name="Button Three" onButtonClick={() => { alert(`You clicked on Button Three! 👆🏻`); }} />
</React.StrictMode>,
document.getElementById('root')
);
Okay so I am trying 9 Simple (But Important) React JS Coding Exercises You Need To Do As A Beginner and am currently on exercise three. I was asked to use TypeScript so I did add it to my project. I just had a meeting where I was told to use onButtonClick as a type and pass it as a prop in my <Button /> component. Right now, what I do not understand and am having trouble figuring out is, where on earth do I pass the onButtonClick in my Button.tsx component. Which is why I am getting the message that I have declared it but I haven't used it. Could someone help me understand where exactly I am supposed to pass it?
Thank you!
The thing is you're not using the onButtonClick prop anywhere in your Button component. You have to use it instead of the hard-coded buttonAlert function that always shows the same message.
Button.tsx
import React from "react";
interface ButtonProps {
name: string;
onButtonClick: () => void;
}
function Button(props: ButtonProps) {
const { name, onButtonClick } = props;
/* Remove the hard-coded alert
function buttonAlert() {
alert(`Testing`);
}*/
/* Change buttonAlert on onClick to the onButtonClick prop */
return (
<div>
<button className="buttonOne" onClick={onButtonClick}>{name}</button>
</div>
);
}
export default Button;

How can I apply Proptypes properly on my React component?

I have the following component:
import React from 'react';
import PropTypes from 'prop-types';
import IconButton from '#material-ui/core/IconButton';
import TextField from '#material-ui/core/TextField';
import ClearIcon from '#material-ui/icons/Clear';
const InputSearch = ({ onClearSearch, onSearch, ...searchProps }) => {
const { id, value } = searchProps;
const onClear = (event) => {
onClearSearch(event, id);
};
return (
<TextField
id={id}
name={id}
onChange={onSearch}
value={value}
InputProps={{
endAdornment: value && (
<IconButton
className={classes.searchIcon}
onClick={onClear}
>
<ClearIcon fontSize={'small'} color={'primary'} />
</IconButton>
),
}}
/>
);
};
InputSearch.propTypes = {
onClearSearch: PropTypes.func.isRequired,
};
export default InputSearch;
As you can see, I'm trying to apply a required validation using propTypes. But then, when I try to use the component without the onClearSearch function, no error is being shown:
<InputSearch
value={searchBy}
onSearch={handleSearch}
/>
So what I'm doing wrong?
Your code is right... you can open developer tool of chrome by pressing F12 -> go to Console and you can see prop type error
for more detail you can see
https://blog.logrocket.com/validating-react-component-props-with-prop-types-ef14b29963fc/
If you want your project give prop-type error in terminal then you have to setup eslint in your project.

withStyles component wrap

I have a following component:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import SnackbarContent from '#material-ui/core/SnackbarContent';
import Snackbar from '#material-ui/core/Snackbar';
const styles = theme => ({
error: {
backgroundColor: theme.palette.error.dark,
}
})
class Snack extends React.Component {
state = {
opendialog: false,
}
constructor(props) {
super(props);
}
test() {
this.setState({opendialog: !this.state.opendialog});
}
render() {
return (
<Snackbar open={this.state.opendialog}>
<SnackbarContent message="test"/>
</Snackbar>
);
}
}
export default withStyles(styles)(Snack);
and app main:
import React, { Component } from 'react';
import Button from '#material-ui/core/Button';
import logo from './logo.svg';
import './App.css';
import Snack from './Snack.js';
class App extends Component {
constructor(props) {
super(props);
this.snack = React.createRef();
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<Button variant="contained" color="primary" onClick={this.handleHello}>Hello World</Button>
<div>
<Snack ref={ ref => this.snack = ref } />
</div>
</div>
);
}
handleHello = () => {
this.snack.test();
}
}
export default App;
I get a "TypeError: _this.snack.test is not a function" when I click the button, however if I drop the withStyles the code works correctly.
I'm just replacing "export default withStyles(styles)(Snack);" line with "export default (Snack);".
Why it does not work correctly with the "withStyles"? How can I make it work?
Because withStyles wraps your component, you need to instead use:
<Snack innerRef={ref => (this.snack = ref)} />
withStyles passes the innerRef property to the wrapped component as ref.
I tried this using the latest version of #material-ui/core (currently 3.8.1). I can't guarantee that older versions support this in the same way.
Here's a fully working example:
The problem is because the withStyles HOC return a new component so you are getting the reference of the HOC. You can use innerRef prop:
<Snack innerRef={ ref => this.snack = ref } />
According to the official documentation:
It adds an innerRef property so you can get a reference to the wrapped component. The usage of innerRef is identical to ref.
You can check it in the official documentation here withStyle function.
I already tested it with your current version it works properly

React Material UI BottomNavigation component Routing Issue

I'm trying to implement the BottomNavigation Component from Material UI and i have an issue with when the user uses the back and forward buttons of the browser.
The problem is, that the BottomNavigation Component is configured to change page in the the layout when a navigation item button is pressed. What it doesn't do however, is change the selected index of the BottomNavigation items when the browser is used to go back to the previous page.
What im left with is an inconsistent state.
How do i go about changing the selected index of the navigation component when the browser buttons are used?
Here is how the UI looks :-
[
Here is the Root Component :-
import React from 'react'
import {browserHistory, withRouter} from 'react-router';
import {PropTypes} from 'prop-types'
import {connect} from 'react-redux'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import AppBar from 'material-ui/AppBar'
import Paper from 'material-ui/Paper'
import MyBottomNavigation from '../material-ui/MyBottomNavigation'
const style = {
padding: 10,
height: '85vh'
}
class Root extends React.Component {
constructor(props) {
super(props)
this.state = {
bottomNavItemIndex : 0
}
}
render() {
return (
<MuiThemeProvider>
<div>
<AppBar title="Pluralsight Admin"
iconClassNameRight="muidocs-icon-navigation-expand-more"
showMenuIconButton={false}
zDepth={1}
/>
<Paper zDepth={1} style={style}>
{this.props.children}
</Paper>
<MyBottomNavigation/>
</div>
</MuiThemeProvider>
)
}
}
Root.propTypes = {
children: PropTypes.object.isRequired
}
export default Root
And here is the Navigation Component :-
class MyBottomNavigation extends Component {
constructor(props){
super(props)
this.state = {
selectedIndex: 0
}
this.selectBottomNavigationItem = this.selectBottomNavigationItem.bind(this)
}
selectBottomNavigationItem(index){
this.setState({selectedIndex: index})
switch(index) {
case 0:
return browserHistory.push('/')
case 1:
return browserHistory.push('/courses')
case 2:
return browserHistory.push('/authors')
default:
return browserHistory.push('/')
}
}
render() {
return (
<Paper zDepth={1}>
<BottomNavigation selectedIndex={this.state.selectedIndex}>
<BottomNavigationItem
label="Home"
icon={recentsIcon}
onClick={() => this.selectBottomNavigationItem(0)}
/>
<BottomNavigationItem
label="Course"
icon={favoritesIcon}
onClick={() => this.selectBottomNavigationItem(1)}
/>
<BottomNavigationItem
label="Authors"
icon={nearbyIcon}
onClick={() => this.selectBottomNavigationItem(2)}
/>
</BottomNavigation>
</Paper>
)
}
}
export default MyBottomNavigation
Just got an implementation working!
The trick is to make a new navbar component that wraps the Material UI BottomNavigation and exports it with the react-router-dom's withRouter higher order function. Then you can do some fiddling with the current route passed into the props and set the value of the BottomNavigation component based on an array of routes (which route corresponds to which value).
My code works a bit differently than what you posted originally, I'm just going off of the BottomNavigation example here and the example of usage with react-router-dom here.
Here is my implementation:
/src/App.js
import React, {Component} from 'react';
import './App.css';
import {BrowserRouter as Router, Route} from 'react-router-dom';
import PrimaryNav from './components/PrimaryNav';
// Views
import HomeView from './views/HomeView';
class App extends Component {
render() {
return (
<Router>
<div className="app">
<Route path="/" component={HomeView} />
<PrimaryNav />
</div>
</Router>
);
}
}
export default App;
/src/components/PrimaryNav.js
import React, {Component} from 'react';
import {Link, withRouter} from 'react-router-dom';
import BottomNavigation from '#material-ui/core/BottomNavigation';
import BottomNavigationAction from '#material-ui/core/BottomNavigationAction';
import LanguageIcon from '#material-ui/icons/Language';
import GroupIcon from '#material-ui/icons/Group';
import ShoppingBasketIcon from '#material-ui/icons/ShoppingBasket';
import HelpIcon from '#material-ui/icons/Help';
import EmailIcon from '#material-ui/icons/Email';
import './PrimaryNav.css';
class PrimaryNav extends Component {
state = {
value: 0,
pathMap: [
'/panoramas',
'/members',
'/shop',
'/about',
'/subscribe'
]
};
componentWillReceiveProps(newProps) {
const {pathname} = newProps.location;
const {pathMap} = this.state;
const value = pathMap.indexOf(pathname);
if (value > -1) {
this.setState({
value
});
}
}
handleChange = (event, value) => {
this.setState({ value });
};
render() {
const {value, pathMap} = this.state;
return (
<BottomNavigation
value={value}
onChange={this.handleChange}
showLabels
className="nav primary"
>
<BottomNavigationAction label="Panoramas" icon={<LanguageIcon />} component={Link} to={pathMap[0]} />
<BottomNavigationAction label="Members" icon={<GroupIcon />} component={Link} to={pathMap[1]} />
<BottomNavigationAction label="Shop" icon={<ShoppingBasketIcon />} component={Link} to={pathMap[2]} />
<BottomNavigationAction label="About" icon={<HelpIcon />} component={Link} to={pathMap[3]} />
<BottomNavigationAction label="Subscribe" icon={<EmailIcon />} component={Link} to={pathMap[4]} />
</BottomNavigation>
);
}
}
export default withRouter(PrimaryNav);
And here's my version numbers for good measure:
"#material-ui/core": "^1.3.1",
"#material-ui/icons": "^1.1.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
Just found a really neat solution for this here:
Essentially you just create a pathname constant each render, using window.location.pathname and make sure that the value prop of each <BottomNavigationAction /> is set to the same as the route (including preceding forward slash) ...something like:
const pathname = window.location.pathname
const [value, setValue] = useState(pathname)
const onChange = (event, newValue) => {
setValue(newValue);
}
return (
<BottomNavigation className={classes.navbar} value={value} onChange={onChange}>
<BottomNavigationAction component={Link} to={'/'} value={'/'} label={'Home'} icon={<Home/>} />
<BottomNavigationAction component={Link} to={'/another-route'} value={'/another-route'} label={'Favourites'} icon={<Favorite/>} />
</BottomNavigation>
)
This means the initial state for value is always taken from the current URL.
I think you should avoid internal state management for this component. If you need to know and highlight the current selected route, you can just use NavigationLink from react-router-dom instead of Link. An "active" class will be added to the corresponding element. You just need to pay attention for the exact prop if you want the navigation element to be active only when an exact match is detected.
[Update] I wrote the wrong component name that is NavLink, not NavigationLink. My bad. Here is the link to the doc https://reactrouter.com/web/api/NavLink

Watching state from child component React with Material UI

New to React. Just using create-react-app and Material UI, nothing else.
Coming from an Angular background.
I cannot communicate from a sibling component to open the sidebar.
I'm separating each part into their own files.
I can get the open button in the Header to talk to the parent App, but cannot get the parent App to communicate with the child LeftSidebar.
Header Component
import React, {Component} from 'react';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationMenu from 'material-ui/svg-icons/navigation/menu';
class Header extends Component {
openLeftBar = () => {
// calls parent method
this.props.onOpenLeftBar();
}
render() {
return (
<AppBar iconElementLeft={
<IconButton onClick={this.openLeftBar}>
<NavigationMenu />
</IconButton>
}
/>
);
}
}
export default Header;
App Component -- receives event from Header, but unsure how to pass dynamic 'watcher' down to LeftSidebar Component
import React, { Component } from 'react';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
// components
import Header from './Header/Header';
import Body from './Body/Body';
import Footer from './Footer/Footer';
import LeftSidebar from './LeftSidebar/LeftSidebar';
class App extends Component {
constructor() {
super() // gives component context of this instead of parent this
this.state = {
leftBarOpen : false
}
}
notifyOpen = () => {
console.log('opened') // works
this.setState({leftBarOpen: true});
/*** need to pass down to child component and $watch somehow... ***/
}
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<Header onOpenLeftBar={this.notifyOpen} />
<Body />
<LeftSidebar listenForOpen={this.state.leftBarOpen} />
<Footer />
</div>
</MuiThemeProvider>
);
}
}
export default App;
LeftSidebar Component - cannot get it to listen to parent App component - Angular would use $scope.$watch or $onChanges
// LeftSidebar
import React, { Component } from 'react';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';
import NavigationClose from 'material-ui/svg-icons/navigation/close';
class LeftNavBar extends Component {
/** unsure if necessary here **/
constructor(props, state) {
super(props, state)
this.state = {
leftBarOpen : this.props.leftBarOpen
}
}
/** closing functionality works **/
close = () => {
this.setState({leftBarOpen: false});
}
render() {
return (
<Drawer open={this.state.leftBarOpen}>
<IconButton onClick={this.close}>
<NavigationClose />
</IconButton>
<MenuItem>Menu Item</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
</Drawer>
);
}
}
export default LeftSidebar;
Free your mind of concepts like "watchers". In React there is only state and props. When a component's state changes via this.setState(..) it will update all of its children in render.
Your code is suffering from a typical anti-pattern of duplicating state. If both the header and the sibling components want to access or update the same piece of state, then they belong in a common ancestor (App, in your case) and no where else.
(some stuff removed / renamed for brevity)
class App extends Component {
// don't need `constructor` can just apply initial state here
state = { leftBarOpen: false }
// probably want 'toggle', but for demo purposes, have two methods
open = () => {
this.setState({ leftBarOpen: true })
}
close = () => {
this.setState({ leftBarOpen: false })
}
render() {
return (
<div className="App">
<Header onOpenLeftBar={this.open} />
<LeftSidebar
closeLeftBar={this.close}
leftBarOpen={this.state.leftBarOpen}
/>
</div>
)
}
}
Now Header and LeftSidebar do not need to be classes at all, and simply react to props, and call prop functions.
const LeftSideBar = props => (
<Drawer open={props.leftBarOpen}>
<IconButton onClick={props.closeLeftBar}>
<NavigationClose />
</IconButton>
</Drawer>
)
Now anytime the state in App changes, no matter who initiated the change, your LeftSideBar will react appropriately since it only knows the most recent props
Once you set the leftBarOpen prop as internal state of LeftNavBar you can't modify it externally anymore as you only read the prop in the constructor which only run once when the component initialize it self.
You can use the componentWillReceiveProps life cycle method and update the state respectively when a new prop is received.
That being said, i don't think a Drawer should be responsible for being closed or opened, but should be responsible on how it looks or what it does when its closed or opened.
A drawer can't close or open it self, same as a light-Ball can't turn it self on or off but a switch / button can and should.
Here is a small example to illustrate my point:
const LightBall = ({ on }) => {
return (
<div>{`The light is ${on ? 'On' : 'Off'}`}</div>
);
}
const MySwitch = ({ onClick, on }) => {
return (
<button onClick={onClick}>{`Turn the light ${!on ? 'On' : 'Off'}`}</button>
)
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
lightOn: false
};
}
toggleLight = () => this.setState({ lightOn: !this.state.lightOn });
render() {
const { lightOn } = this.state;
return (
<div>
<MySwitch onClick={this.toggleLight} on={lightOn} />
<LightBall on={lightOn} />
</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>

Resources