Connect function outside the state in React - javascript

I have an application with many items collection, and in admin area, the admin user can delete or modify items (like a traditional e-commerce website back office). In logic, for modify an item, user click on "modify item" button, and a modal open.
My problem is i can't open modal on click button, because my browser show me an error message when I click on "modify item" button : "TypeError: Cannot read property 'setState' of undefined"
I use react-modal-responsive for this task.
I have been stuck for several days, I would be super happy if you could help me. Thank in advance.
This is my code:
import React, { Component } from 'react';
import { database } from '../firebase/firebase';
import * as firebase from 'firebase';
import withAuthorization from './withAuthorization';
import _ from 'lodash';
import AuthUserContext from './AuthUserContext';
import FileUploader from "react-firebase-file-uploader";
import Image from 'react-image-resizer';
import{InstantSearch, SearchBox, Hits, Highlight, RefinementList} from "react-instantsearch/dom";
import Modal from 'react-responsive-modal';
function removeToCatalogue(hit) {
const item = hit.objectID;
firebase.database().ref(`catalogue/${item}`).remove();
console.log("Capsule correctement supprimée du catalogue")
}
const Hit = ({hit}) =>
<div className="item">
<img src={hit.avatarURL} width={150} height={150}></img>
<h1 className="marque">{hit.marque}</h1>
<h3 className="numero">{hit.numero}</h3>
<h4 className="reference">{hit.reference}</h4>
<h4 className="marquesuite">{hit.marquesuite}</h4>
<p className="cote">{hit.cote}</p>
<button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button>
<button onClick={() => this.setState({open: true})}>Modify Item</button>
</div>
const Content = () =>
<div className="text-center">
<Hits hitComponent={Hit}/>
</div>
class Admin extends React.Component {
constructor (props) {
super (props);
this.state = {
marque: '',
marquesuite: '',
numero: '',
reference: '',
cote: '',
avatar: "",
isUploading: false,
progress: 0,
avatarURL: "",
catalogue: {},
open: false,
};
this.onInputChange = this.onInputChange.bind(this);
this.onHandleSubmit = this.onHandleSubmit.bind(this);
}
onOpenModal = () => {
this.setState({ open: true });
};
onCloseModal = () => {
this.setState({ open: false });
};
onInputChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
onHandleSubmit(e){
e.preventDefault();
const catalogue = {
marque: this.state.marque,
marquesuite: this.state.marquesuite,
numero: this.state.numero,
reference: this.state.reference,
cote: this.state.cote,
avatar: this.state.avatar,
avatarURL: this.state.avatarURL,
};
database.push(catalogue);
this.setState({
marque: '',
marquesuite: '',
numero: '',
reference: '',
cote: '',
avatar: "",
isUploading: false,
progress: 0,
avatarURL: "",
});
}
handleUploadStart = () => this.setState({ isUploading: true, progress: 0 });
handleProgress = progress => this.setState({ progress });
handleUploadError = error => {
this.setState({ isUploading: false });
console.error(error);
};
handleUploadSuccess = filename => {
this.setState({ avatar: filename, progress: 100, isUploading: false });
firebase
.storage()
.ref("images")
.child(filename)
.getDownloadURL()
.then(url => this.setState({ avatarURL: url }));
};
render (){
const { open } = this.state;
return (
<div className="container-fluid">
<div className="container">
<h1 class="text-center">Espace d'Administration</h1>
Signaler une modification
<form onSubmit={this.onHandleSubmit}>
<div className="form-group">
<label>Marque de la capsule:</label>
<input
value={this.state.marque}
type="text"
name='marque'
placeholder="Marque"
onChange={this.onInputChange}
ref="marque"
className="form-control" />
</div>
<div>
<label>Numéro de la capsule:</label>
<input
value={this.state.numero}
type="text"
name='numero'
placeholder="Numéro de la capsule"
onChange={this.onInputChange}
ref="numero"
className="form-control"/>
</div>
<div className="form-group">
<label>Référence de la capsule:</label>
<input
value={this.state.marquesuite}
type="text"
name='marquesuite'
placeholder="Référence de la capsule"
onChange={this.onInputChange}
ref="marquesuite"
className="form-control"/>
</div>
<div className="form-group">
<label>Référence de la capsule (suite):</label>
<input
value={this.state.reference}
type="text"
name='reference'
placeholder="Référence de la capsule (suite)"
onChange={this.onInputChange}
ref="reference"
className="form-control"/>
</div>
<div className="form-group">
<label>Cote de la capsule:</label>
<input
value={this.state.cote}
type="text"
name='cote'
placeholder="Cote de la capsule"
onChange={this.onInputChange}
ref="cote"
className="form-control"/>
</div>
<label>Visuel de la capsule:</label>
{this.state.isUploading && <p>Progress: {this.state.progress}</p>}
{this.state.avatarURL && <img src={this.state.avatarURL} />}
<FileUploader
accept="image/*"
name="avatar"
randomizeFilename
storageRef={firebase.storage().ref("images")}
onUploadStart={this.handleUploadStart}
onUploadError={this.handleUploadError}
onUploadSuccess={this.handleUploadSuccess}
onProgress={this.handleProgress}
/>
<button className="btn btn-info">Ajouter une capsule</button>
</form>
</div>
<h1 className="text-center">Catalogue de capsule</h1>
<InstantSearch
apiKey="xxx"
appId="xxx"
indexName="xxx">
<SearchBox translations={{placeholder:'Rechercher une capsule'}} width="500 px"/>
<div>
<Modal open={this.state.showModal} open={open} onClose={this.onCloseModal} center>
<h2>Simple centered modal</h2>
</Modal>
</div>
<Content />
</InstantSearch>
</div>
)
}
}
const authCondition = (authUser) => !!authUser;
export default withAuthorization(authCondition)(Admin);

I don't know if this answer might entirely solve your problem, but it'll at least let you open the modal.
First, you need to pass an handler when rendering the Content component, something like:
<Content onEdit={ this.onOpenModal } />
Then, we should have a similar handler on the Hit component, attaching it to the button click event:
const Hit = ({ hit, onEdit }) =>
<div className="item">
<img src={hit.avatarURL} width={150} height={150}></img>
<h1 className="marque">{hit.marque}</h1>
<h3 className="numero">{hit.numero}</h3>
<h4 className="reference">{hit.reference}</h4>
<h4 className="marquesuite">{hit.marquesuite}</h4>
<p className="cote">{hit.cote}</p>
<button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button>
<button onClick={ onEdit }>Modify Item</button>
</div>
Now we have to pass down this handler from the Content component to the Hit one.
The problem I see is that the Hits component take a hitComponent prop which has to be a component to be rendered. This means that, to pass the onEdit handler, we have to enhance the Hit component with a Javascript clojure before passing it down:
const Content = ({ onEdit }) => {
const EnhancedHit = props =>
<Hit onEdit={ onEdit } { ...props } />
return (
<div className="text-center">
<Hits hitComponent={ EnhancedHit } />
</div>
)
}
Can you try and report if it opens the modal?

A component's state is enclosed within the component that defines it. In your case you are trying to update the state of the Admin through Hit component. From React.js State and Life Documentation
State is similar to props, but it is private and fully controlled by
the component.
But the reason this error is thrown is not because Hit can't update the state of Admin, it doesn't event try to. What happens when you click the Modify Item button is Hit is trying to update its own state, but this won't work for two reasons:
First and foremost from the same documentation:
We mentioned before that components defined as classes have some additional features. Local state is exactly that: a feature available only to classes.
Even if you transform the component to a class component, the state should be defined either in the constructor or as class field. But this won't help you anyway, because although you will be able to update the state, it would be Hit's one.
So, if I were you, you would go for one of the following solutions (depending on the case).
Inline Content, Hits and Hit within Admin's render method or extract them as renderContent and renderHits Admin's class field methods, this way you will be able to update the state within this methods methods.
Probably the one you should consider more: Pass openModel class field function as onModifyItem prop down to Hits component, and use it as onClick prop for Modify Item button.
Here is an example:
const Hit = ({onModifyItem, hit}) =>
<div className="item">
<img src={hit.avatarURL} width={150} height={150}></img>
<h1 className="marque">{hit.marque}</h1>
<h3 className="numero">{hit.numero}</h3>
<h4 className="reference">{hit.reference}</h4>
<h4 className="marquesuite">{hit.marquesuite}</h4>
<p className="cote">{hit.cote}</p>
<button className="btn btn-danger" onClick={() => removeToCatalogue(hit)}>Supprimer</button>
<button onClick={onModifyItem}>Modify Item</button>
</div>
const Content = ({ onModifyItem }) => {
const HitComponent = props => <Hit onModifyItem={onModifyItem} {...props}/>
return (<div className="text-center">
<Hits hitComponent={HitComponent}/>
</div>);
}
<Content onModifyItem={this.openDialog}/>

Related

Input value not changing on setState

Working with React, in my function setTitleAndBody that you can see below, when I console.log the object result I get the title and body information displayed in the developer pannel but the setState is not working and I can't find out why. Maybe something is interacting with it so it cannot be displayed in my input sections but I can't seem to find it.
import React, { Component } from 'react';
import firebase from 'firebase/app';
class TextEditor extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
noteTitre: '',
noteCorps: ''
};
this.handleTitleChange = this.handleTitleChange.bind(this);
this.handleBodyChange = this.handleBodyChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.clearInput = this.clearInput.bind(this);
this.setTitleAndBody = this.setTitleAndBody.bind(this);
}
handleTitleChange(event) {
this.setState({noteTitre: event.target.value});
}
handleBodyChange(event) {
this.setState({noteCorps: event.target.value});
console.log(this.state)
}
handleSubmit(event) {
let postData = {
title: this.state.noteTitre,
body: this.state.noteCorps
}
var dbRef = firebase.database().ref();
dbRef.push(postData);
this.setState({noteTitre: ""});
this.setState({noteCorps: ""});
alert("Note sauvegardée!")
}
clearInput() {
this.setState({noteTitre: ""});
this.setState({noteCorps: ""});
}
setTitleAndBody(result) {
console.log(result)
this.setState({noteTitre: result.title});
this.setState({noteCorps: result.body});
}
render() {
return (
<div className="writing">
<div className="form-group">
{/* Titre */}
<input
name="noteTitre"
className="form-control"
placeholder="Titre de la note"
value={this.state.noteTitre}
onChange={this.handleTitleChange} />
<br/>
{/* Corps */}
<textarea
name="noteCorps"
className="form-control"
placeholder="Corps de la note"
value={this.state.noteCorps}
onChange={this.handleBodyChange} />
</div>
<div className="text-right">
{/* Bouton d'annulation */}
<button
onClick={this.clearInput}
className="btn btn-danger mr-2 rounded">
Annuler
</button>
{/* Bouton de sauvegarde */}
<button
onClick={this.handleSubmit}
className="btn btn-success ml-2 rounded"
id="bouton_sauvegarde">
Sauvegarder
</button>
</div>
</div>
);
}
}
export default TextEditor;
destructuring title and body will solve the problem,
better approach:
setTitleAndBody(result) {
const {title, body} = result;
this.setState({...this.state, noteTitre:title, noteCorps:body });
}
follow above approach every where you are setting a state such as:
handleTitleChange(event) {
this.setState({...this.state, noteTitre: event.target.value});
}
handleBodyChange(event) {
this.setState({...this.state, noteCorps: event.target.value});
}
``
I did not see where you use setTitleAndBody in this code.
And how do you know that state is not change?
How you test it?
if you decide that is not changed because printing with console.log(this.state) it is not gonna work, because setState is asynchronous.
you can provide the console.log as callback to setState. setState(updater, callback) to see the right value.
setTitleAndBody(result) { this.setState({noteTitre: result.title}, () => console.log(this.state)); ........ }

How to pass Props() value into setState() to make modal form editable using ReactJs

I am creating my blog where an user can able to edit his/her blogs.
Here I am popping the data into a modal to make this edit..
So after all debugging and a better thinking I have figure this out . This is my updated working code for modal
Thank you for your time.
//blog.js
class Blogs extends Component{
constructor(props) {
super(props);
this.state = {
modal: false,
justClicked: null,
activePage: 1,
requiredItem : null,
_id: '',
blog_short_desc:'',
blog_name: '',
blog_desc: '',
blog_image_link: '',
blog_by: '',
blog_by_author: ''
};
this.handleOpenDialog = this.handleOpenDialog.bind(this);
this.handleCloseDialog = this.handleCloseDialog.bind(this);
this.replaceModalItem = this.replaceModalItem.bind(this);
this.onTodoChange = this.onTodoChange.bind(this);
}
static propTypes = {
getBlog: PropTypes.func.isRequired,
deleteBlog: PropTypes.func.isRequired,
updateBlog: PropTypes.func.isRequired,
resume: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
loading: PropTypes.object.isRequired
}
toggle = (id) => {
this.setState({
modal: !this.state.modal
});
}
componentDidMount() {
this.props.getBlog();
}
replaceModalItem(id, blog_short_desc, blog_name , blog_desc, blog_image_link, blog_by, blog_by_author) {
this.setState({
modal: true,
requiredItem: id,
_id: id,
blog_short_desc: blog_short_desc,
blog_name: blog_name,
blog_desc: blog_desc,
blog_image_link: blog_image_link,
blog_by: blog_by,
blog_by_author: blog_by_author
});
}
onTodoChange = (e) => {
this.setState({
[e.target.name] : e.target.value
});
}
onSubmit = (e, id) => {
e.preventDefault();
const updatedBlog = {
blog_short_desc: this.state.blog_short_desc,
blog_name: this.state.blog_name,
blog_desc: this.state.blog_desc,
blog_image_link: this.state.blog_image_link,
blog_by: this.props.auth["user"]._id,
blog_by_author: this.props.auth["user"].name
}
//update blog via updateblog action
this.props.updateBlog(id, updatedBlog);
alert("Blog updated successfully!");
e.target.reset();
this.toggle();
window.location.reload();
}
handleOpenDialog(id) {
this.setState({
openDialog: true,
OpenEditDialog: true,
justClicked: id
});
}
handleCloseDialog() {
this.setState({
openDialog: false
});
}
onDeleteBlogClick = (id) => {
this.props.deleteBlog(id);
};
handlePageChange(pageNumber) {
this.setState({activePage: pageNumber});
}
render(){
const { blogs, loading} = this.props.resume;
const { user, isAuthenticated } = this.props.auth;
const itemsPerPage = 6;
let activeBlogs = blogs.slice (itemsPerPage * this.state.activePage - itemsPerPage, itemsPerPage * this.state.activePage);
return(
<Container>
{loading ? (
<div><Loading/></div>
) : (
<div>
{/* blog modal */}
<BlogModal />
{/* card dialog */}
<BlogData blogs={blogs} user={this.props.auth} handleCloseDialog={this.handleCloseDialog} {...this.state} toggle={this.toggle}/>
{/* edit card dialog */}
<EditBlog onTodoChange={this.onTodoChange} {...this.state} toggle={this.toggle} onSubmit={this.onSubmit}/>
<Grid style={{padding: 0}} className="blog-grid">
{activeBlogs.map((item, i) => (
<Cell key={item._id} data-id={item._id} className="blog-grid-cell">
<Card shadow={5} className="cards-grid">
{item.blog_image_link ?
(<CardTitle style={{color: '#fff', height: '200px',
width: 'auto', backgroundImage: `url(${item.blog_image_link})`, backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat'}}></CardTitle>) :
(<CardTitle className="card-title-image"></CardTitle>
)
}
<CardText>
<b>{item.blog_short_desc}</b>
</CardText>
<CardActions border>
<p className="block-data-details">
<Button className="blog-read-me-button col-4" onClick={this.handleOpenDialog.bind(this, item._id)}>Read </Button>
{ isAuthenticated === true && (item.blog_by === user._id) ?
<span className="col=8">
<Button className="remove-btn-blog-post"
color="danger"
size="sm"
onClick= {this.onDeleteBlogClick.bind(this, item._id)} title="Delete Blog">
×
</Button>
<a className="btn edit-btn-blog-post" href="#"
onClick={this.replaceModalItem.bind(this, item._id, item.blog_short_desc, item.blog_name, item.blog_desc, item.blog_image_link, item.blog_by, item.blog_by_author )} title="Edit Blog">
<i className="fa fa-pencil" aria-hidden="true"></i>
</a>
</span> : null }
</p>
<p style={{ fontWeight:'bold'}}>By-{item.blog_by_author} <span style={{float:'right',}}>{Moment(item.date).format('Do MMMM YYYY')}</span></p>
</CardActions>
</Card>
</Cell>
))}
</Grid>
</div>
)}
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={6}
totalItemsCount={blogs.length}
pageRangeDisplayed={5}
onChange={this.handlePageChange.bind(this)}
itemClass='page-item'
linkClass='page-link'
/>
</Container>
)
}
}
const mapStateToProps = (state) => ({
resume: state.resume,
auth: state.auth,
loading: state.apiCallsInProgress > 0
});
export default connect(mapStateToProps, {getBlog, deleteBlog, updateBlog }) (Blogs);
//Edit.js
const EditBlog = ({ toggle, onTodoChange, onSubmit, ...state}) => {
return(
<span>
<Modal
isOpen = {state.modal && state.requiredItem === state._id}
toggle = {()=>this.toggle(state._id)}
>
<ModalHeader toggle={toggle} style={{fontWeight: "bold"}}>
Edit your blog {state.blog_name}
</ModalHeader>
<ModalBody>
<Form onSubmit={e => onSubmit(e, state._id )}>
<FormGroup>
<Label for="blogHeading">Blog Heading</Label>
<Input type="text" name="blog_short_desc" id="blogHeading" placeholder="Update one liner"
onChange={onTodoChange} defaultValue={state.blog_short_desc}/>
<Label for="blogName">Blog Name</Label>
<Input type="text" name="blog_name" id="blogName" placeholder="Update blog name"
onChange={onTodoChange} defaultValue={state.blog_name}/>
<Label for="desc1">Description </Label>
<Input type="textarea" name="blog_desc" id="desc1" placeholder="Update your blog"
onChange={onTodoChange} defaultValue={state.blog_desc}/>
<Label for="imageUrl">Image Url</Label>
<Input type="text" name="blog_image_link" id="imageUrl" placeholder="Update image url (Optional)"
onChange={onTodoChange} defaultValue={state.blog_image_link}/>
<Button
color="dark"
style={{marginTop: '2rem'}}
block
>Edit blog</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</span>
)
}
const mapStateToProps = state => ({
resume: state.resume,
auth: state.auth
})
export default connect(mapStateToProps, { updateBlog })(EditBlog);
//CurrentUI of working edit
First, the uneditable bug is caused by using "value" props for your input in modal inside of "defaultValue". If you use value, you are always giving it the value of initial props. Use default value. Using defaultValue makes it a controlled component. Read more on Stackoverflow here. Change that and see next issues if any.
Second, ensure you avoid UNSAFE_componentWillReceiveProps(). Looking at your componentWillRecieveProps method, what is said in the documentation may be at play:
"Calling this.setState() generally doesn’t trigger UNSAFE_componentWillReceiveProps()."
Update:
Remove the componentWillReceiveProps method. submit should work. And ensure user is authenticated.
It looks to me like your onTodoChange function is setting state on the parent Blogs component, but that state doesn't make it back to the input values. Instead, the parent passes a blogs prop into EditBlogs, and since onTodoChange never affects blogs, the input's value remains unchanged.
This means your inputs' onChange event values (e.target.value) never make it back to the inputs' value attribute, so the input doesn't actually change values.
Since the blog_ values in state and onTodoChange are all local to the edit form, I recommend moving those down to that level.. Blogs doesn't need to know about that stuff, and it'll simplify things - onTodoChange will set event values into state, which will flow right back into the inputs as values.
Your blogs prop should only set the initial state.
When a user is editing their blog, take their blog information and create an object like this
userBlogData={
blog_heading: 'users blog heading',
blog_name: 'users blog name',
description: 'users blog description',
image_url: 'users blog image url',
}
Update the modal form based on these objects. For example:
<input name="blog_heading" value={blog_heading} ... />
After the user have edited the object you can make an update request on your server and call the get function at the same time for updating the blogs. You can keep the update function on the edit component. But the get function will be passed as a props.
Hope This Helps
This should give you the idea...........................
// Edit Component
this.state = {
blog_name:this.props.data.blog_name
}
onBlogUpdate = () => {
let payload = this.state
API CALL...
}
...
render(){
return(
<input value={this.state.blog_name} name='blog_name' onChange={...} ... />
)
}
I have figure it out with
OnClick of edit button I passed all required data along with it.
So, my replaceModalItem() binds all data together in setState()
In replaceModalItem i called those data and set it to setState().
In Edit.js I have called all the state values.
Hence getting all the required values in input field setting it with defaultValue

React - Mapped array not passing props correctly to child component

I am making a dashboard component which displays rendered previews and code for HTML snippets. Inside of the dashboard component I am mapping the array of snippets using .map. Each mapped snippet is going to have a delete function (already built) and an update function.
For the update function to work each snippet has it's own child modal component. I need to pass the ID of the snippet to the modal component where I can combine the ID with the new content before updating the database and state.
However, I'm making a mistake somewhere as I pass the ID as props to the modal.
.map used inside of my Dashboard.js Dashboard class component.
{this.state.snippets.map(snippet => (
<>
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p> //THIS WORKS
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button type="button" onClick={this.showModal}>
Open
</button>
</div>
</div>
</div>
<Modal
sid={snippet._id} //PASS ID HERE
show={this.state.show}
handleClose={this.hideModal}
></Modal>
</>
))}
This renders the snippets below (3 snippet pods, with their database ID included).
The open button opens the modal (Modal.js) below.
import React, { Component } from 'react'
import api from '../api'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
handleInputChange = event => {
this.setState({
[event.target.name]: event.target.value,
})
}
handleClick = id => event => {
event.preventDefault()
console.log(id)
}
render() {
const { sid, show, handleClose } = this.props
console.log(sid)
const showHideClassName = show ? 'modal display-flex' : 'modal display-none'
return (
<div id="Modal" className={showHideClassName}>
<div id="modal-main">
<h4>Edit snippet {sid}</h4>
<form>
Name:{' '}
<input
type="text"
value={this.state.name}
name="name"
onChange={this.handleInputChange}
/>{' '}
<br />
Details:{' '}
<input
type="text"
value={this.state.details}
name="details"
onChange={this.handleInputChange}
/>{' '}
<br />
Content:{' '}
<textarea
value={this.state.content}
name="content"
cols="30"
rows="10"
onChange={this.handleInputChange}
/>{' '}
<br />
<button onClick={this.handleClick(sid)}>TEST ME</button>
</form>
<button onClick={handleClose}>Close</button>
{this.state.message && (
<div className="info">{this.state.message}</div>
)}
</div>
</div>
)
}
}
The console.log just under the render actually pastes the correct 3 ID's the console.
However, calling the ID (sid) within the Modal.js return will only show the last snippet ID, no matter which Modal I open. The same goes for pushing that ID to the handleClick function where I intend to combine the ID with an update package.
Solution below as initiated by HMR in the comments.
The problem was all the modals were showing and just the last one was visible.
Fixed by moving the modal out of the .map and instead updating the ID from within the .map to the state and passing the state ID to a new nested component within the modal.
Also switched to using dynamic CSS to show and hide the modal based on the state.
Dashboard.jsx
export default class Snippets extends Component {
constructor(props) {
super(props)
this.showModal = React.createRef()
this.state = {
snippets: [],
show: false,
sid: '',
}
}
handleDelete = id => event => {
event.preventDefault()
api
.deleteSnippet(id)
.then(result => {
console.log('DATA DELETED')
api.getSnippets().then(result => {
this.setState({ snippets: result })
console.log('CLIENT UPDATED')
})
})
.catch(err => this.setState({ message: err.toString() }))
}
handleModal = id => {
this.setState({ sid: id })
this.showModal.current.showModal()
}
//<div id="preview">{ReactHtmlParser(snippet.content)}</div>
render() {
return (
<>
<Modal ref={this.showModal} handleClose={this.hideModal}>
<ModalUpdate sid={this.state.sid} />
</Modal>
<div className="Dashboard">
<div className="wrapper">
<div className="container">
<div className="holder">
<div className="content">
<div className="content__body">
<h3>Dashboard</h3>
</div>
</div>
</div>
<div className="break"></div>
{this.state.snippets.map(snippet => (
<div key={snippet._id} className="holder--pod">
<div className="content">
<div className="content__snippet-preview">
Snippet preview
</div>
<div className="content__body">
<h4>{snippet.name}</h4>
<p>{snippet.details}</p>
<p>{snippet._id}</p>
<pre>
<code>{snippet.content}</code>
</pre>
</div>
<div className="content__button">
<button onClick={this.handleDelete(snippet._id)}>
Delete
</button>
<button
type="button"
onClick={() => this.handleModal(snippet._id)}
>
Open
</button>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</>
)
}
Modal.jsx
import React, { Component } from 'react'
export default class Modal extends Component {
constructor(props) {
super(props)
this.state = {
show: false,
}
}
showModal = () => {
this.setState({ show: true })
}
hideModal = () => {
this.setState({ show: false })
}
render() {
return (
<div
id="Modal"
style={{ display: this.state.show === true ? 'flex' : 'none' }}
>
<div id="modal-main">
<h4>Edit snippet </h4>
{this.props.children}
<button onClick={() => this.hideModal()}>Close</button>
</div>
</div>
)
}
}
ModalUpdate.jsx
import React, { Component } from 'react'
export default class ModalUpdate extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
details: '',
content: '',
message: null,
}
}
// handleInputChange = event => {
// this.setState({
// [event.target.name]: event.target.value,
// })
// }
// handleClick = id => event => {
// event.preventDefault()
// console.log(id)
// }
render() {
return <h4>ID = {this.props.sid}</h4>
}
}
I am not sure about the handleDelete function,. but replacing the line should solve the issue probably
<button onClick={() => this.handleDelete(snippet._id)}>
One potential issue is the this.handleDelete(snippet._id) will fire immediately rather than onClick, so you will need to add an anonymous function in the event listener:
() => this.handleDelete(snippet._id)
instead of
this.handleDelete(snippet._id)

Failed prop type: You provided a `value` prop to a form field. React-Bootstrap-Typehead

I am using the bootstrap-typehead:https://github.com/ericgio/react-bootstrap-typeahead and I cannot figure out why this package is freaking out. Whats wrong with this code that it gives me this error:
Failed prop type: You provided a value prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultValue. Otherwise, set either onChange or readOnly.
import React, { Component } from "react";
import Dropdown from "../../bootstrap/Dropdown";
import RealmAPIs from "../../../API/realms/RealmAPI";
import {Typeahead} from 'react-bootstrap-typeahead';
import AutoComplete from "../../bootstrap/AutoComplete";
// import RealmAPI from '../../../API/realms/RealmAPI';
var options = [
'John',
'Miles',
'Charles',
'Herbie',
];
export default class FindCharacter extends Component {
state = {
realmName: "",
characterName: "",
realms: []
};
setRealmName = value => {
this.setState({ realmName: value });
};
componentDidMount() {
// let realms = [...this.state.realms];
// RealmAPIs.getAllRealms().then(response =>
// console.log(response.realms.map(value => {}))
// );
}
render() {
return (
<div>
<form className="form-inline justify-content-md-center">
<div className="form-group mb-2">
{/* <Dropdown setRealmName={this.setRealmName}/> */}
<Typeahead
labelKey="name"
placeholder="Type a realm"
onChange={selected => {
console.log(selected);
}}
options={
options
}
/>
</div>
<div className="form-group mx-sm-3 mb-2">
<input
type="text"
className="form-control"
id="characterName"
placeholder="Enter Character Name"
/>
</div>
<button
type="submit"
className="btn btn-primary mb-2"
onClick={e => {
e.preventDefault();
console.log(this.state.name);
}}
>
Submit
</button>
</form>
</div>
);
}
}
Upgrading to v3.2.3 of react-bootstrap-typeahead should solve the issue.
I'm not sure why, but React v16.5 (I think?) started triggering the warning. There was an issue tracking it as well as a PR fixing it.

Using a button to open a dialog box to get some user input

I'm using React and firebase to create a simplified slack and using MDl for styles. I'm trying to integrate a button that opens up a dialog box to get some user input, in this case the name of a new chat room, and then when submitted it will send the data to firebase and store it in the rooms array and display the new room name in the list. I have already set up the form to get user input and then I tried to refactor it to work in a dialog box and I seem stuck on how to get the dialog box to work. Here is my whole component:
import React, { Component } from 'react';
import './RoomList.css';
import dialogPolyfill from 'dialog-polyfill';
class RoomList extends Component {
constructor(props) {
super(props);
this.state = { rooms: [] };
this.roomsRef = this.props.firebase.database().ref('rooms');
this.handleChange = this.handleChange.bind(this);
this.createRoom = this.createRoom.bind(this);
}
handleChange(e){
this.setState({ name: e.target.value });
}
createRoom(e) {
e.preventDefault();
this.roomsRef.push({ name: this.state.name });
this.setState({ name: "" });
}
componentDidMount() {
this.roomsRef.on('child_added', snapshot => {
const room = snapshot.val();
room.key = snapshot.key;
this.setState({ rooms: this.state.rooms.concat( room ) });
})
}
dialogBox(e){
const dialogButton = document.getElementsByClassName('dialog-
button');
const dialog = document.getElementById('dialog');
if (! dialog.showModal) {
dialogPolyfill.registerDialog(dialog);
}
dialogButton.onClick( (e) => {
dialog.showModal();
});
dialog.document.getElementsByClassName('submit-close').onCLick(
(e) => {
dialog.close();
});
}
render() {
const roomlist = this.state.rooms.map( (room) =>
<span className="mdl-navigation__link" key={room.key}>
{room.name}</span>
);
const newListForm = (
<div id="form">
<button className="mdl-button mdl-js-button mdl-button--
raised mdl-js-ripple-effect dialog-button">Add room</button>
<dialog id="dialog" className="mdl-dialog">
<h3 className="mdl-dialog__title">Create Room name</h3>
<div className="mdl-dialog__content">
<p>Enter a room name</p>
</div>
<div className="mdl-dialog__actions">
<form onSubmit={this.createRoom}>
<div className="mdl-textfield mdl-js-textfield">
<input type="text" value={this.state.name}
className="mdl-textfield__input" id="rooms" onChange=
{this.handleChange} />
<label className="mdl-textfield__label"
htmlFor="rooms">Enter room Name...</label>
<button type="submit" className="mdl-button submit-
close">Submit</button>
<button type="button" className="mdl-button submit-
close">Close</button>
</div>
</form>
</div>
</dialog>
</div>
);
return (
<div className="layout mdl-layout mdl-js-layout mdl-layout--
fixed-drawer mdl-layout--fixed-header">
<header className="header mdl-layout__header mdl-color--
grey-100 mdl-color-text--grey-600">
<div className="mdl-layout__header-row">
<span className="mdl-layout-title">Bloc Chat</span>
<div className="mdl-layout-spacer"></div>
</div>
</header>
<div className="drawer mdl-layout__drawer mdl-color--blue-
grey-900 mdl-color-text--blue-grey-50">
<header className="drawer-header">
<span>{newListForm}</span>
</header>
<nav className="navigation mdl-navigation mdl-color--
blue-grey-800">
<div>{roomlist}</div>
<div className="mdl-layout-spacer"></div>
</nav>
</div>
</div>
);
}
}
export default RoomList;
Here is a simple sample on how to build a modal with the new portal API provided from React v16.xx
Working demo can be found here. Just use the dropdown to navigate to the simple portal demo. A snapshot of the full code base can be found on github.
Working Code
import React, { Component } from "react";
import { createPortal } from "react-dom";
import "./simple-portal.css";
export default class SimplePortal extends Component {
constructor() {
super();
this.state = {
list: [],
input: "",
showDialog: false
};
this._onChange = this._onChange.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
_onChange(e) {
let input = e.target.value;
this.setState({ input });
}
_onSubmit(e) {
e.preventDefault();
let showDialog = false;
// Dont Mutate the State!!!
let list = this.state.list.slice();
list.push(this.state.input);
this.setState({ showDialog, list, input: "" });
}
render() {
const { showDialog, list, input } = this.state;
return (
<div className="container">
<div>
<button
className="btn"
onClick={e =>
this.setState({
showDialog: !showDialog
})
}
>
Add Item
</button>
</div>
{/* Render Items from List */}
<div>
<ul>
{list.map(item => {
return <li key={item}>{item}</li>;
})}
</ul>
</div>
{/* Show Modal - Renders Outside React Hierarchy Tree via Portal Pattern */}
{showDialog === true ? (
<DialogModal>
<div className="dialog-wrapper">
<h1>New List Item</h1>
<form onSubmit={this._onSubmit}>
<input type="text" value={input} onChange={this._onChange} />
</form>
</div>
</DialogModal>
) : null}
</div>
);
}
}
class DialogModal extends Component {
constructor() {
super();
this.body = document.getElementsByTagName("body")[0];
this.el = document.createElement("div");
this.el.id = "dialog-root";
}
componentDidMount() {
this.body.appendChild(this.el);
}
componentWillUnmount() {
this.body.removeChild(this.el);
}
render() {
return createPortal(this.props.children, this.el);
}
}
I don't see any event listeners on your buttons that would trigger a rendering of the modal. I would approach this by specifying an onClick event that would update the state which would render the modal/dialogue box.
Another solution, which may be the way you are thinking, is to have the state change to render the modal/dialogue box as visible from within the createRoom() function. Remember, updating state or getting new props will trigger a rendering of the component. You are trying to update the state to re-render your component with the modal/dialogue being shown.
Sorry if I misunderstood the question or your goal.

Categories