I have a Modal dialog component that takes another component as content and returns a promise for handling the result (Idea is from here). How can I extract the 'time' state from the content in order to add it to the promise chain?
import React from 'react'
import Modal from './Modal'
class Late extends React.Component {
constructor(props) {
super(props);
this.state = {
time: '18:48'
};
}
render() {
return (
<div className='modal-body'>
{this.time}
</div>
);
}
}
export default function(message, options) {
var form = <Late description={options.description} />;
return Modal(form, message, options).then(() => ??);
}
import React from 'react'
import ReactDOM from 'react-dom'
import '../css/modal.css'
import Promise from 'bluebird'
import _ from 'lodash'
Promise.config({ cancellation: true });
class Modal extends React.Component {
constructor(props) {
super(props);
this.resolve = null;
}
abort = () => this.promise.cancel();
confirm = () => this.resolve();
componentDidMount() {
this.promise = new Promise(resolve => this.resolve = resolve);
return ReactDOM.findDOMNode(this.refs.confirm).focus();
}
backdrop = () => <div className='modal-backdrop in' />;
modal() {
var style = {display: 'block'};
return (
<div
className='modal in'
tabIndex='-1'
role='dialog'
aria-hidden='false'
ref='modal'
style={style}
>
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
<h4 className='modal-title'>
{this.props.message}
</h4>
</div>
{this.props.children}
<div className='modal-footer'>
<div className='text-right'>
<button type='button' className='btn btn-default' onClick={this.abort} >
{this.props.abortLabel}
</button>
{' '}
<button type='button' className='btn btn-primary' ref='confirm' onClick={this.confirm} >
{this.props.confirmLabel}
</button>
</div>
</div>
</div>
</div>
</div>);
}
render() {
return (
<div>
{this.backdrop()}
{this.modal()}
</div>
);
}
}
export default function(content, message, options) {
var cleanup, component, props, wrapper;
if (options == null) {
options = {};
}
props = _.assign({
message: message
}, options);
wrapper = document.body.appendChild(document.createElement('div'));
component = ReactDOM.render(<Modal {...props}>{content}</Modal>, wrapper);
cleanup = function() {
ReactDOM.unmountComponentAtNode(wrapper);
return setTimeout(function() {
return wrapper.remove();
});
};
return component.promise.finally(cleanup);
};
if i understood you correctly, you wish to make some action once the Modal is done(pressed OK i.e). If so ,than you could wrap the Modal with a component and pass a function as a prop to the Modal so that once the Modal component is handling the wished action it can omit the function from prop.
take a look how to pass a function as a prop, and Lift up the state
Related
I am making a Todo App in React JS, i'm fairly new to React JS.
I have made two components named AddComponent and TodoItem component. The AddComponent has an input and button to add the todos and it will render TodoItem as a child component whenever a todo is added using the AddComponent.
I also have to remove the child component TodoItem when a button inside it is clicked according to it's key, so it would remove that item from the state of the parent component AddComponent.
The problem i'm facing is i can't change the state anywhere else than the render() function, because there is where i'm dynamically generating the TodoItem components using the state.
And changing the state inside the render() function would result in an infinite loop. Please help how to resolve this problem.
I'm using Bootstrap 4 using a CDN for styling.
AddComponent.js
import React from 'react'
import shortid from 'shortid'
import TodoItem from './TodoItem';
class AddComponent extends React.Component {
constructor() {
super();
this.state = {
todoText: '',
todoList: []
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleTodoClick = this.handleTodoClick.bind(this);
}
handleChange(e) {
e.persist();
this.setState((prevState) => {
return {
todoText: e.target.value,
todoList: prevState.todoList
}
})
}
handleClick() {
this.setState((prevState) => {
return {
todoText: prevState.todoText,
todoList: [{
text: prevState.todoText,
id: shortid.generate()
}, ...prevState.todoList]
}
})
}
handleTodoClick(id) {
const newState = this.state.todoList.filter(todoL => todoL.id === id);
this.setState({ todoList: newState });
}
render() {
const todos = this.state.todoList.map((todo) => {
return (
<TodoItem key={todo.id} value={todo.text} click={this.handleTodoClick(todo.id)}>{todo.text}</TodoItem>
)
})
return (
<div>
<div className="row">
<div className="col-sm-9">
<div className="form-group">
<input
type="text"
className="form-control"
placeholder="Add a Todo"
onChange={this.handleChange}
/>
</div>
</div>
<div className="col-sm-3">
<button
type="button"
className="btn btn-primary btn-block"
onClick={this.handleClick}
>Add
</button>
</div>
</div>
<div>
{todos}
</div>
</div>
)
}
}
export default AddComponent
TodoItem.js
import React from 'react'
class TodoItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="alert alert-secondary alert-dismissible fade show">
<button
type="button"
className="close"
onClick={this.props.click}
data-dismiss="alert">×</button>
{this.props.value}
</div>
)
}
}
export default TodoItem
Try : https://codesandbox.io/s/hopeful-moon-duqfe
There were two problems; 1st one is filter and the 2nd one is handler callback function that is set to click prop.
1st problem : Filter function should filter that does not match your id so you should use NOT equal :
handleTodoClick(id) {
const newState = this.state.todoList.filter(todoL => todoL.id !== id);
this.setState({ todoList: newState });
}
2nd problem : When you set click prop as a handler callback function, you should set reference of the function itself without calling it. Not like this : click={this.handleTodoClick(todo.id). This will call the function and set the click prop to result of it, which is undefined since it does not return to anything.
So i have changed your click props as click={this.handleTodoClick}
const todos = this.state.todoList.map(todo => {
return (
<TodoItem key={todo.id} id={todo.id} value={todo.text} click={this.handleTodoClick}>
{todo.text}
</TodoItem>
);
});
On child component, onClick is similary set to function itself onClick={() => this.props.click(this.props.id)} NOT like onClick={this.props.click(this.props.id)}.
import React from "react";
class TodoItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="alert alert-secondary alert-dismissible fade show">
<button
type="button"
className="close"
onClick={() => this.props.click(this.props.id)}
data-dismiss="alert"
>
×
</button>
{this.props.value}
</div>
);
}
}
Fix:
<TodoItem key={todo.id} todoId={todo.id} value={todo.text} click={this.handleTodoClick(todo.id)}>{todo.text}</TodoItem>
We can't use key as id. The key property is used by React under the hood, and is not exposed to us.
import React from 'react'
class TodoItem extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="alert alert-secondary alert-dismissible fade show">
<button
type="button"
className="close"
// look here
onClick={e => {this.props.click(this.props.todoId)}}
data-dismiss="alert">×</button>
{this.props.value}
</div>
)
}
}
export default TodoItem
Try the following by updating the handle for click in AddComponent then creating a handler within TodoItem that calls props click with the respective todo item id value. I'd recommend to just pass the entire todo so that you can access both the unique id and value in TodoItem:
AddComponent:
const todos = this.state.todoList.map((todo) => {
return (
<TodoItem key={todo.id} todo={todo} click={this.handleTodoClick}>{todo.text}</TodoItem>
)
})
TodoItem:
import React from 'react'
class TodoItem extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.click(this.props.todo.id);
}
render() {
return (
<div className="alert alert-secondary alert-dismissible fade show">
<button
type="button"
className="close"
onClick={this.handleClick}
data-dismiss="alert">×</button>
{this.props.todo.value}
</div>
)
}
}
export default TodoItem;
I am trying to save the value of the button as a string.If i click residence button it will save the value in categoryName as 'residence' or 'commercial' and redirect to another page .I have built a Rest API in the backend to bind and save the value in database.The code is something like this
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
class CustomizedButtons extends React.Component {
constructor(props) {
super(props);
this.state = {
apiUrl:config.publicRuntimeConfig.publicRuntimeConfigValue.apiUrl,
category: " ",
};
}
saveValue = () => {
console.log('savecategory');
axios.post( this.state.apiUrl+'/api/v1/leadsurvey/category', {
'categoryName':this.state.category,
}, {})
};
render() {
const { classes} = this.props;
return (
<div>
<div>
<p>What is the type of your property?</p>
<div>
<button onClick={() => this.saveValue()}>Residence</button>
<button onClick={() => this.saveValue()}>Commercial</button>
</div>
<div style={{marginTop: '90px'}}>
</div>
</div>
</div>
);
}
}
export default CustomizedButtons;
I am not getting how to make it work to bind and save.In case of saving form value i did something like this.
this.state = {
apiUrl:config.publicRuntimeConfig.publicRuntimeConfigValue.apiUrl,
FreeQuoteName :"",
};
this.handleFreeQuoteName = this.handleFreeQuoteName.bind(this);
saveFreeQuote = () => {
console.log('saveFreeQuote ...', this.state);
axios.post( this.state.apiUrl+'/api/v1/SalesLead/save', {
'name': this.state.FreeQuoteName,
}
}
handleFreeQuoteName(event) { this.setState({ FreeQuoteName: event.target.value }); }
<Form>
<p>Name*</p>
<input maxLength="30" onChange={this.handleFreeQuoteName} value={this.state.FreeQuoteName}
type="text" placeholder="Enter name here"/>
<div style={{textAlign:'center', marginTop:'35px', marginBottom:'22px'}} className={card.disable}>
<button disabled={isDisabled} type="button" fullwidth="true" variant="contained"
onClick={() => this.saveFreeQuote()} style={{padding: '9px 0px'}}>Submit</button>
</Form>
I want to do same for the value button.If i click the button it will save the value as a string and redirect to another page.How can i do it?
from your post I assumed that you want to save button value in state and also want to initiate the axios request while button click.
try to change like below
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import axios from 'axios';
class CustomizedButtons extends React.Component {
constructor(props) {
super(props);
this.state = {
apiUrl:config.publicRuntimeConfig.publicRuntimeConfigValue.apiUrl,
category: "",
};
}
saveValue = (e) => {
console.log('savecategory', e.target.innerHTML);
this.setState({
category: e.target.innerHTML
}, this.makeAxiosRequest);
};
makeAxiosRequest = () => {
axios.post( this.state.apiUrl+'/api/v1/leadsurvey/category', {
'categoryName':this.state.category,
}, {})
};
render() {
const { classes} = this.props;
return (
<div>
<div>
<p>What is the type of your property?</p>
<div>
<button onClick={this.saveValue}>Residence</button>
<button onClick={this.saveValue}>Commercial</button>
</div>
<div style={{marginTop: '90px'}}>
</div>
</div>
</div>
);
}
}
export default CustomizedButtons;
here am using callback function inside setState() to initiate axios request after button value saved in state.
Hope this helps.
When a user like a post, then the count is incremented in ui. Bu when a user remove the like then count ui is not changing though it is changed in server.How to solve it?
When a user like a post, then the count is incremented in ui. Bu when a user remove the like then count ui is not changing though it is changed in server.How to solve it?
import React from 'react';
import moment from 'moment';
import CommentForm from './CommentForm';
import CommentList from './CommentList';
import CommentModal from './CommentModal';
import { connect } from 'react-redux';
import { startAddComment, startAddLike, startRemoveLike } from
'../actions/post';
import { Link } from 'react-router-dom';
import UserInfo from './UserInfo';
class PostListItem extends React.Component{
constructor(props){
super(props);
this.state = {
isliked: false,
commentM: undefined,
likes: this.props.likes
}
}
componentDidMount(){
if(this.props.likes.includes(this.props.user.uid)){
this.setState(() => ({isliked:true}));
}
}
onClickedLike = () =>{
if(this.state.isliked === false){
this.props.dispatch(startAddLike(this.props._id));
this.setState(()=>{
console.log(this.props);
return{
isliked:true
}
});
} else{
this.props.dispatch(startRemoveLike(this.props._id));
this.setState(()=>({isliked:false}));
}
}
openModal = () =>{
this.setState({commentM: this.props.comments});
}
closeModal = () =>{
this.setState(({commentM: undefined}));
}
render(){
return(
<div className="post">
<div className="post__header">
<UserInfo user={this.props.author}
time={this.props.createdAt}/>
{
(this.props.user.uid === this.props.author.uid)?
<Link to={`/edit/${this.props._id}`}
className="post__edit">
Edit</Link>:''
}
{/* <p className="post__time">
{moment(this.props.createdAt).fromNow()}</p> */}
</div>
<div className="post__caption">{this.props.caption}</div>
<img src={this.props.content} className="post__content"/>
<div className="post__extra">
<div className="post__lc">
<button className="post__button"
onClick={this.onClickedLike}
>{this.state.isliked? <i className="fas fa-futbol"></i>
: <i className="far fa-futbol"></i>}
</button>
<button className="post__button"
onClick={this.openModal}><i className="far fa-
comment"></i>
</button>
</div>
{this.props.likes.length !== 0 && <p className="post__like">
{this.props.likes.length} {this.props.likes.length === 1? 'like':'likes'}
</p>} // likes count is not changing while removing the like(ui only)
<CommentModal
commentM={this.state.commentM}
closeModal={this.closeModal}/>
<CommentForm onSubmit={(comment) => {
this.props.dispatch(startAddComment(this.props._id,
comment));
}} />
{this.props.comments && <CommentList comments=
{this.props.comments}/>}
</div>
</div>
);
}
};
const mapStateToProps = (state) => {
return{
user: state.auth
}
}
export default connect(mapStateToProps)(PostListItem);
While trying to toggle between two child components, I need to have the trigger button in the child component, and pass the click function through the child component to the in order to toggle the other child component. I'm not sure how to push the props from the child to the parent in order to trigger the toggle.
Parent component
import React from 'react'
import CancelOffer from '../CancelPages/CancelOffer'
import CancelWarning from '../CancelPages/CancelWarning'
class Cancel extends React.Component {
constructor() {
super()
this.state = {
isHidden: true
}
}
toggleOffer() {
this.setState({
isHidden: !this.state.isHidden
})
}
render() {
return (
<div className = 'cancel'
style = {{backgroundImage: `url(${this.props.backgroundImage})`}} >
<div className = 'container' >
{!this.state.isHidden &&
<CancelOffer { ...this.props}/>
}
{this.state.isHidden &&
<CancelWarning { ...this.props}/>
}
{this.state.isHidden &&
<button onClick = {this.toggleOffer.bind(this)} > Click < /button>
}
</div>
</div>
)
}
}
export default Cancel
Child component
import React from 'react'
import SvgIcon from '../SvgIcon/SvgIcon'
import './CancelWarning.scss'
function CancelOffer (props) {
const content = props.config.contentStrings
return (
<div className='cancel-warning'>
<h2 className='heading md'>heading</h2>
<p className='subpara'>subheading</p>
<div className='losses'>
<ul>
<li>text</li>
<li>text</li>
<li>text</li>
</ul>
</div>
<div className='footer-links'>
<a href='/member' className='btn btn--primary btn--lg'>continue</a>
<a href='/cancel' className='cancel-link'>Cancel</a>
//NEED TO HAVE BUTTON HERE AND PASS PROPS TO PARENT TO TOGGLE VIEW
{this.state.isHidden &&
<button onClick = {this.toggleOffer.bind(this)}>Click</button>
}
</div>
</div>
)
}
export default CancelOffer
You can just pass it like regular param. Also, you can use arrow functions instead of binding.
Parent component
import React from 'react'
import CancelOffer from '../CancelPages/CancelOffer'
import CancelWarning from '../CancelPages/CancelWarning'
class Cancel extends React.Component {
constructor() {
super()
this.state = {
isHidden: true
}
this.toggleOffer = this.toggleOffer.bind(this);
}
toggleOffer() {
this.setState({
isHidden: !this.state.isHidden
})
}
render() {
const { isHidden } = this.state
return (
<div className = 'cancel'
style = {{backgroundImage: `url(${this.props.backgroundImage})`}} >
<div className = 'container' >
{!isHidden &&
<CancelOffer toggleOffer={this.toggleOffer} isHidden={isHidden}/>
}
{isHidden &&
<CancelWarning toggleOffer={this.toggleOffer} isHidden={isHidden}/>
}
{isHidden &&
<button onClick = {this.toggleOffer}> Click </button>
}
</div>
</div>
)
}
}
export default Cancel
Child component
import React from 'react'
import SvgIcon from '../SvgIcon/SvgIcon'
import './CancelWarning.scss'
function CancelOffer (props) {
const content = props.config.contentStrings
return (
<div className='cancel-warning'>
<h2 className='heading md'>heading</h2>
<p className='subpara'>subheading</p>
<div className='losses'>
<ul>
<li>text</li>
<li>text</li>
<li>text</li>
</ul>
</div>
<div className='footer-links'>
<a href='/member' className='btn btn--primary btn--lg'>continue</a>
<a href='/cancel' className='cancel-link'>Cancel</a>
{props.isHidden &&
<button onClick = {props.toggleOffer}>Click</button>
}
</div>
</div>
)
}
export default CancelOffer
I'm sorry if this is a frequent question but it's something I'm reallly struggling to understand.
I'm building a basic to-do list app in React.
The container currently contains a form that takes in the information, and adds each item inputted an items array in the state. I then have a 'TaskList' component that takes this state and renders my tasks.
What I want to do is create a separate form component, instead of having the form within my container.
The issue is that if I just copy the code for the form into a new component, the state it will modify is its own, and therefore won't be accessible via the TaskList component to render the list of tasks.
Is there any way to have a component that can update the state of its parent component. My source code is below for reference.
export default class Container extends React.Component {
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
items: []
}
}
handleSubmit(e) {
e.preventDefault();
var itemsArray = this.state.items;
itemsArray.push(e.target.elements.task.value);
this.setState({
items: itemsArray
})
e.target.reset();
}
render() {
return (
<div className="">
<header className="header">TODO</header>
<form onSubmit={this.handleSubmit}>
<input name="task" placeholder="Task"></input>
<button type="submit">Add</button>
</form>
<TaskList data={this.state.items} />
</div>
);
}
}
<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>
const TaskList = props => {
var tasks = (props.data).map( (item, key) => { return <Task data={item} key={key} /> })
return(
<ul className="gif-list">
{tasks}
</ul>
);
}
export default TaskList;
<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>
You can do it this way. Pass the parent function that change the state as a props to the form component. In the form handle submit function, call the parent function as this.props.addTodo(todoText).
Container.js
import React, { Component } from 'react';
import TaskList from './TaskList';
import Form from './Form';
export default class Container extends Component {
constructor() {
super();
this.handleAdd = this.handleAdd.bind(this);
this.state = {
items: []
}
}
handleAdd(todoText) {
var itemsArray = this.state.items;
itemsArray.push(todoText);
this.setState({
items: itemsArray
})
}
render() {
return (
<div className="">
<header className="header">TODO</header>
<Form addTodo={this.handleAdd}/>
<TaskList data={this.state.items} />
</div>
);
}
}
Form.js
import React, { Component } from 'react';
export default class Form extends Component {
handleSubmit(e) {
e.preventDefault();
let todoText = e.target.elements.task.value;
if(todoText.length > 0) {
e.target.elements.task.value = '';
this.props.addTodo(todoText);
}else{
e.target.elements.task.focus();
}
}
render() {
return(
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<input name="task" placeholder="Task"></input>
<button type="submit">Add</button>
</form>
</div>
);
}
}
TaskList.js
import React from 'react';
const TaskList = props => {
var tasks = (props.data).map( (item, key) => { return <li key={key}>{item}</li> })
return(
<ul className="gif-list">
{tasks}
</ul>
);
}
export default TaskList;