Type is invalid -- expected a string - javascript

I am building a Meteor app with ReactJS and Semantic-UI for react. I have run into a problem when trying to create a form for a new item that appears as a modal. I receive the following error.
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in. Check the render method of `App`.
App.jsx file:
import React, { Component, PropTypes } from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Items } from '../api/items.js';
import { Item } from './Item.jsx';
import { ItemFormModal } from './ItemFormModal.jsx';
// App component - represents the whole app
export class App extends Component {
renderItems() {
return this.props.items.map((item) => (
<Item key={item._id} item={item} />
));
}
render() {
return (
<div className="container">
<header>
<h1>Products</h1>
<ItemFormModal />
</header>
<ul className="collection">
{this.renderItems()}
</ul>
</div>
);
}
}
App.propTypes = {
items: PropTypes.array.isRequired,
};
// creates container on client side for the collection
export default createContainer(() => {
return {
// fetch all the items available
items: Items.find({}, { sort: { createdAt: -1 } }).fetch(),
};
}, App);
EDIT: I have changed it to reflect the whole ItemFormModal.jsx:
import { Items } from '../api/items.js';
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
// Import all semantic resources
import { Button, Icon, Header, Form, Modal } from 'semantic-ui-react';
export default class ItemFormModal extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
price: 0.00,
modalOpen: false
}
this.handleOpen = this.handleOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleOpen(event) { this.setState({ modalOpen: true }) }
handleClose(event) { this.setState({ modalOpen: false }) }
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
let title = this.state.title.trim();
let price = this.state.price;
Items.insert({
title: title,
price: price,
recurring: false,
createdAt: new Date(), // current time
});
this.setState({
title: "",
price: 0.00
});
}
render() {
return (
<div id="new-item">
<Button onClick={this.handleOpen}>Create</Button>
<Modal
open={this.state.modalOpen}
onClose={this.handleClose}
size="small"
closeIcon="close">
<Modal.Header>Create new item</Modal.Header>
<Modal.Content>
<Form>
<Form.Group>
<Form.Input name="title" label="Title" placeholder="Title" value={this.state.title} onChange={this.handleInputChange}/>
<Form.Input name="price" label="Price" placeholder="Price" value={this.state.price} onChange={this.handleInputChange} />
</Form.Group>
</Form>
</Modal.Content>
<Modal.Actions>
<Button className="negative" onClick={this.handleClose}>Cancel</Button>
<Button className="positive" onClick={this.handleSubmit}>Create</Button>
</Modal.Actions>
</Modal>
</div>
)
}
}
Any help is greatly appreciated!

you are importing into App.jsx incorrectly. you have this:
import { ItemFormModal } from './ItemFormModal.jsx';
... which will not work if your export is marked as default. you can either remove "default" from your export, or you can change your import to this:
import ItemFormModal from './ItemFormModal.jsx';

Related

React screen keyboard from class to function component

I am using the react-screen-keyboard library and there is the following code example:
import React, {Component, PropTypes} from 'react';
import Keyboard, {KeyboardButton} from 'react-screen-keyboard';
export default class Input extends Component {
static propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func,
}
static defaultProps = {
value: '',
onFocus: null,
}
state = {
inputNode: null,
}
handleInput = (event) => this.props.onChange(event.target.value)
handleFocus = () => {
if (this.props.onFocus) {
this.props.onFocus(this.input);
this.setState({inputNode: this.input});
// the `this.refs.input` value should be passed to the Keyboard component as inputNode prop
}
}
render() {
const {value} = this.props;
const {inputNode} = this.state;
return (
<div>
<input
onInput={this.handleInput}
value={value}
onFocus={this.handleFocus}
ref={(input) => { this.input = input; }}
/>
<Keyboard
inputNode={inputNode}
rightButtons={[
<ClickOnKeyPressWrap key="enter">
<KeyboardButton
onClick={this.handleLoginUser}
value="Войти"
classes="keyboard-submit-button"
/>
</ClickOnKeyPressWrap>
]}
/>
</div>
);
}
}
however, I haven't worked with React class components, and the PropTypes approach has long been deprecated. Tell me, how can I rewrite this example on a functional component?

getting material-ui TextField value onsubmit

I want to get the value of TextField input and render the message conditionally. I tried this one, its working but this one is functioning dynamically because I used onChange. I want to achieve the same but using onSubmit on <Button> Is there anyway to do that?
import React from 'react';
import { Component } from 'react';
import Button from '#mui/material/Button';
import { TextField } from '#mui/material';
class App extends Component
{
state = {
myValue: null,
}
handleChange = (e) => this.setState({
myValue: e.target.value
})
render() {
return (
<div>
<TextField
value={this.state.myValue}
onSubmit={this.handleChange}
/>
<button >Get Weather</button>
{this.state.myValue ? <p>value inputed </p>: <p>no input</p>}
</div>
)
}
}
export default App;
Using Refs is what you need. You can get the current value of your input by clicking a button and only then change the state.
Demo
import React, { createRef } from "react";
import { Component } from "react";
import { TextField } from "#mui/material";
class App extends Component {
constructor(props) {
super(props);
this.textInput = createRef();
this.state = {
myValue: ""
};
}
showRefContent = () => {
this.setState({
myValue: this.textInput.current.value
});
};
handleChange = (e) =>
this.setState({
myValue: e.target.value
});
render() {
return (
<div>
<TextField inputRef={this.textInput} />
<button onClick={this.showRefContent}>Get Weather</button>
<p>
{this.state.myValue.length > 0
? `text:${this.state.myValue}`
: "no text"}
</p>
</div>
);
}
}
export default App;
you just need to you onkeydown instead onsubmit.
<TextField
value={this.state.myValue}
onKeyDown={this.handleChange}
/>
or use
<form onSubmit={handleChange }>
<TextField
value={this.state.myValue}
onKeyDown={this.handleChange}
/>
<button type="submit">submit</button>
</form>

Why does my Component not update after posting data to REST service

So I am trying to build my first react.js app and am wondering, why my page does neither update after POSTING a new player to my app via axios, nor after DELETING one. As I refresh the page afterwards the players are created or gone depending on the operation. So the API part is working just fine.
I have a PlayerPage.js like so:
PlayerPage.js
import React, { Component } from 'react';
import axios from 'axios';
import PlayerForm from './playerform';
import PlayCard from './playercard';
class PlayerPage extends Component {
constructor(props) {
super(props);
}
state = {
players: []
};
componentDidMount() {
axios
.get('http://localhost:3001/player')
.then(res => {
const players = res.data;
this.setState({ players });
})
.catch(console.log);
}
render() {
return (
<div>
<h2>Add Player</h2>
<PlayerForm />
<hr></hr>
<h2>Available Player</h2>
{this.state.players.map(player => (
<PlayCard player={player} key={player.id} />
))}
</div>
);
}
}
export default PlayerPage;
It does reference (and fill in from API) the PlayerForm and PlayerCard component, which look like this:
playerform.js
import React, { Component } from 'react';
import axios from 'axios';
import {
FormControl,
FormGroup,
FormLabel,
Form,
Button
} from 'react-bootstrap';
class PlayerForm extends Component {
state = {
name: '',
nickname: ''
};
handleSubmit = e => {
e.preventDefault();
axios
.post('http://localhost:3001/player', JSON.stringify(this.state), {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
.then()
.catch(err => console.log(err));
};
handelChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
name="name"
placeholder="Enter player name"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<FormGroup>
<FormLabel>Nickname</FormLabel>
<FormControl
type="text"
name="nickname"
placeholder="Enter player nickname"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<Button variant="btn btn-primary" type="submit">
Add
</Button>
</Form>
);
}
}
export default PlayerForm;
and playercard.js:
import React, { Component } from 'react';
import { Card, Button } from 'react-bootstrap';
import PropTypes from 'prop-types';
import axios from 'axios';
class PlayerCard extends Component {
constructor(props) {
super(props);
this.player = props.player;
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
axios
.delete(`http://localhost:3001/player/${this.player.id}`)
.then()
.catch(console.log);
}
render() {
return (
<Card className="flex-row flex-wrap">
<Card.Header>
<Card.Img variant="left" src="https://via.placeholder.com/150" />
</Card.Header>
<Card.Body variant="center">
<Card.Title>{this.player.name}</Card.Title>
<Card.Subtitle>{this.player.nickname}</Card.Subtitle>
</Card.Body>
<Card.Body variant="right">
<Button variant="btn btn-primary" onClick={this.handleClick}>
Delete
</Button>
</Card.Body>
</Card>
);
}
}
PlayerCard.propTypes = {
player: PropTypes.object.isRequired
};
export default PlayerCard;
So the question is, why does my side not update properly?
I am grateful for any hints. Thanks in advance.
Bonus question: How do I fetch the ip and port dynamically depending on where the node server is running?
Why it's not working:
Because you are not refreshing the list view when the form is submitted.
React is not magic, you need to tell when you want the view to be rerender.
One way to to this is adding a onCreate prop to your playerform.js and run the function when the form is successfully submitted.
import React, { Component } from 'react';
import axios from 'axios';
import PlayerForm from './playerform';
import PlayCard from './playercard';
class PlayerPage extends Component {
constructor(props) {
super(props);
}
state = {
players: []
};
loadPlayers() {
axios
.get('http://localhost:3001/player')
.then(res => {
const players = res.data;
this.setState({ players });
})
.catch(console.log);
}
componentDidMount() {
this.loadPlayers();
}
render() {
return (
<div>
<h2>Add Player</h2>
<PlayerForm onCreate={() => this.loadPlayers()}/>
<hr></hr>
<h2>Available Player</h2>
{this.state.players.map(player => (
<PlayCard player={player} key={player.id} />
))}
</div>
);
}
}
export default PlayerPage;
import React, { Component } from 'react';
import axios from 'axios';
import {
FormControl,
FormGroup,
FormLabel,
Form,
Button
} from 'react-bootstrap';
class PlayerForm extends Component {
state = {
name: '',
nickname: ''
};
handleSubmit = e => {
e.preventDefault();
axios
.post('http://localhost:3001/player', JSON.stringify(this.state), {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
.then(() => {
this.props.onCreate();
})
.catch(err => console.log(err));
};
handelChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
name="name"
placeholder="Enter player name"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<FormGroup>
<FormLabel>Nickname</FormLabel>
<FormControl
type="text"
name="nickname"
placeholder="Enter player nickname"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<Button variant="btn btn-primary" type="submit">
Add
</Button>
</Form>
);
}
}
export default PlayerForm;
I didn't check if the prop is present but normally you should.
How to handle API route
You should use environments variables:
.env
API_HOST=http://localhost:3001/
process.env.API_HOST
But it's depending on your development environment.
As all suggested, you are not updating the state of the PlayerPage component. Even though answer suggested by #tristan works, I would suggest to reduce API calls as much as possible. My solution would be as following:
PlayerPage.js
import React, { Component } from 'react';
import axios from 'axios';
import PlayerForm from './playerform';
import PlayCard from './playercard';
class PlayerPage extends Component {
constructor(props) {
super(props);
}
state = {
players: []
};
componentDidMount() {
axios
.get('http://localhost:3001/player')
.then(res => {
const players = res.data;
this.setState({ players });
})
.catch(console.log);
}
handleAddNewPlayer = player => {
this.setState(prevState => ({
players: [...prevState.players, player] // or [player, ...prevState.players]
}))
}
handleRemovePlayer = id => {
const players = this.state.filter(player => player.id !== id)
this.setState({ players })
}
render() {
return (
<div>
<h2>Add Player</h2>
<PlayerForm handleAddNewPlayer={this.handleAddNewPlayer}/>
<hr></hr>
<h2>Available Player</h2>
{this.state.players.map(player => (
<PlayCard player={player} key={player.id} handleRemovePlayer={this.handleRemovePlayer} />
))}
</div>
);
}
}
export default PlayerPage;
playerform.js
import React, { Component } from 'react';
import axios from 'axios';
import {
FormControl,
FormGroup,
FormLabel,
Form,
Button
} from 'react-bootstrap';
class PlayerForm extends Component {
state = {
name: '',
nickname: ''
};
handleSubmit = e => {
e.preventDefault();
axios
.post('http://localhost:3001/player', JSON.stringify(this.state), {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
.then(data => this.props.handleAddNewPlayer(data)) // adjust this api call to return added user with id
.catch(err => console.log(err));
};
handelChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
render() {
return (
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
type="text"
name="name"
placeholder="Enter player name"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<FormGroup>
<FormLabel>Nickname</FormLabel>
<FormControl
type="text"
name="nickname"
placeholder="Enter player nickname"
onChange={this.handelChange.bind(this)}
/>
</FormGroup>
<Button variant="btn btn-primary" type="submit">
Add
</Button>
</Form>
);
}
}
export default PlayerForm;
and playercard.js:
import React, { Component } from 'react';
import { Card, Button } from 'react-bootstrap';
import PropTypes from 'prop-types';
import axios from 'axios';
class PlayerCard extends Component {
constructor(props) {
super(props);
this.player = props.player;
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
axios
.delete(`http://localhost:3001/player/${this.player.id}`)
.then(this.props.handleRemovePlayer(this.player.id))
.catch(console.log);
}
render() {
return (
<Card className="flex-row flex-wrap">
<Card.Header>
<Card.Img variant="left" src="https://via.placeholder.com/150" />
</Card.Header>
<Card.Body variant="center">
<Card.Title>{this.player.name}</Card.Title>
<Card.Subtitle>{this.player.nickname}</Card.Subtitle>
</Card.Body>
<Card.Body variant="right">
<Button variant="btn btn-primary" onClick={this.handleClick}>
Delete
</Button>
</Card.Body>
</Card>
);
}
}
PlayerCard.propTypes = {
player: PropTypes.object.isRequired
};
export default PlayerCard;
The lifecycle method componentDidMount() doesn't run in PlayerPage.js every time the state changes, just once when it first loads onto the page. This means the GET request is only run once when the page loads.
You need to either call the GET route again after a POST or DELETE happens, or update the app's state with the data from the POST/DELETE call when it happens to put that data onto the page as well as into the database.
To update the PlayerCard list, the players state must be updated in PlayerPage.
Since the parent has no way to get new players after the Rest API call, it seems that we need a callback function to update the parent component as a child.
I assume you are not using any state management tool(Redux, Flux), and only react is in place and also you don't want to call the api again.
Inside PlayerPage component make a function
addPlayer=(player)=>{
this.setState({
players: {...players, player}
})
}
}
and in render of the same component
Inside PlayerCard
handleClick() {
axios
.delete(`http://localhost:3001/player/${this.player.id}`)
.then((response)=>{
you get your output pass it to props
var player=response.data
this.props.addPlayer(response)
})
.catch(console.log);
}
This example is only with reference to the addPlayer . You can customise it according to your requirement

Redux Form doesn't change values

Trying to make a form with Redux Form.
Here is my component
import React, { Component } from 'react'
import { Field, reduxForm } from 'redux-form'
import Form from './components/Form'
import TextFieldForReduxForm from './components/TextFieldForReduxForm'
import validate from './validate'
import { signUpCallbacks } from './onSubmit'
class SignUp extends Component {
render () {
return (
<Form
{...this.props}
formTitle='SIGN UP'
buttonTitle='SIGN UP'
linkTo='/sign/in'
linkTitle='Sign In'
>
<Field
component={TextFieldForReduxForm}
name='email'
label='Email'
margin='normal'
/>,
<Field
component={TextFieldForReduxForm}
name='password'
label='Password'
type='password'
margin='normal'
/>
</Form>
)
}
}
export default reduxForm({
form: 'signup',
validate,
onSubmit: signUpCallbacks.onSubmit,
onChange: values => console.log('onChange', values)
})(SignUp)
The problem is that on submit I always get required errors, which means the values somehow never change. And onChange is not called as well.
Here is validate.js
export const isValidEmail = email => /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(email)
const validate = values => {
console.log('validate', values)
const errors = {}
if (!values.email) {
errors.email = 'Required'
} else if (!isValidEmail(values.email)) {
errors.email = 'Invalid email address'
}
if (!values.password) {
errors.password = 'Required'
}
return errors
}
export default validate
And here is onSubmit.js
export const signUpCallbacks = {
onSubmit (values, dispatch, props) {
console.log(values)
}
}
Here are the components
Form.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '#material-ui/core/styles'
import Grid from '#material-ui/core/Grid'
import Typography from '#material-ui/core/Typography'
import Button from '#material-ui/core/Button'
import { Link } from 'react-router'
const styles = {
button: {
margin: '15px 0px'
}
}
class Form extends Component {
static propTypes = {
classes: PropTypes.object,
formTitle: PropTypes.string,
buttonTitle: PropTypes.string,
linkTo: PropTypes.string,
linkTitle: PropTypes.string,
children: PropTypes.array,
handleSubmit: PropTypes.func,
submitting: PropTypes.bool
}
render () {
const {
classes,
formTitle,
buttonTitle,
linkTo,
linkTitle,
children,
submitting,
handleSubmit
} = this.props
return (
<form onSubmit={handleSubmit}>
<Grid
container
spacing={16}
alignItems='center'
direction='column'
justify='center'
>
<Typography variant='headline' gutterBottom>
{formTitle}
</Typography>
{children}
<Button
disabled={submitting}
type='submit'
variant='contained'
color='primary'
className={classes.button}
>
{buttonTitle}
</Button>
<Link to={linkTo}>{linkTitle}</Link>
</Grid>
</form>
)
}
}
export default withStyles(styles)(Form)
TextField
import React from 'react'
import PropTypes from 'prop-types'
import TextField from '#material-ui/core/TextField'
const TextFieldForReduxForm = props => {
const {
meta: {
touched,
error
},
label
} = props
const isErrored = error && touched
const displayErrorOrLabel = () => {
if (isErrored) return error
return label
}
return <TextField
{...props}
error={isErrored}
label={displayErrorOrLabel()}
/>
}
export default TextFieldForReduxForm
TextFieldForReduxForm.propTypes = {
meta: PropTypes.object,
label: PropTypes.string
}
The values, event handlers and other input props from redux-form's Field component are passed on to the form component inside props.input
Spread props.input instead of props inside the TextFieldForReduxForm component.

Prop is marked as required in component, but its value is `undefined`

single.js :
import React, { Component } from 'react';
import Details from '../components/details'
import { ProgressBar } from 'react-materialize';
import { Route, Link } from 'react-router-dom';
const Test = () => (
<div> RENDER PAGE 1</div>
)
class SinglePage extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
}
}
componentDidMount() {
fetch('http://localhost:1337/1')
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
render() {
const { data } = this.state;
return (
<div>
<h2> SinglePage </h2>
{!data ? (
<ProgressBar />
) : (
<div>
<Details data={data} />
</div>
)}
</div>
);
}
}
export default SinglePage;
details.js :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Details extends Component {
static propTypes = {
item: PropTypes.shape({
date: PropTypes.string.isRequired,
}).isRequired,
}
render() {
const { item } = this.props;
return (
<div>
<p> {item.date} </p>
</div>
)
}
}
export default Details;
In console, I am getting an error : Warning: Failed prop type: The prop item is marked as required in Details, but its value is undefined.
From this I though my json was not catched but I have an other component which fetch on http://localhost:1337/ , get datas and display them correctly, and going to http://localhost:1337/1 send me a json response so I'm quite confused here.
Additional screenshot :
SinglePage is passing date props with name data as oppose to item that is defined in Details
<Details item={date} />
Also adding init value for date
constructor(props) {
super(props);
this.state = {
date: { date: null },
}
}

Categories

Resources