I have an entity with a date that within a datepicker component I need to change for a new date. To do that I'm ussing a popup. The popup work flawlessly if I click the datepicker and handle the change with the function this.props.handleDateChange but if the users types in the date with the keyboard I can't catch the new date and modify my parent state with the value in my datepicker this.props.myEntityCurrentDate. How can I achieve this?
I have the following HOC that corresponds to an popup in react. It receives a body and a footer.
const createPopup = (Body, Footer) => {
class Popup extends React.PureComponent {
render() {
return <div className='popup-react'>
<div className='popup-react-content'>
<div>
<div>
<span>{this.props.title}</span>
</div>
<div>
<Body {...this.props} />
</div>
<div>
<Footer {...this.props} />
</div>
</div>
</div>
</div>;
}
}
return Popup;
}
And I create the following body and footer for the desired page
class PopupBody extends React.PureComponent {
render() {
return <div>
<DatePicker
dateFormat="MM/DD/YYYY"
value={this.props.myEntityCurrentDate}
onChange={this.props.handleDateChange}
/>
</div>;
}
}
class PopupFooter extends React.PureComponent {
render() {
return <div>
<button type="button">
<span onClick={() => this.props.handleSubmit(this.props.myEntityId)}>Accept</span>
</button>
<button type="button" onClick={this.props.closePopup}>
<span className='ui-button-text'>Cancel</span>
</button>
</div>;
}
}
const Popup = createPopup(PopupBody, PopupFooter);
The component that will make use of those components and render the popup is the following:
class MyEntity extends React.Component {
constructor(props) {
super(props);
this.state = {
showPopup: true,
newDate: null,
errors: []
};
//bind of this
};
}
handlePopupDateChange(dueDate) {
this.setState({
newDate: dueDate,
errors: []
});
}
handlePopupSubmit(id) {
var validations = {
newDate: {
presence: {
allowEmpty: false, message: " can't be empty"
}
}
};
validate(this.state, validations, () => this.submitPopupForm(id), this.renderPopupErrors);
}
submitPopupForm(id) {
var vm = { Id: parseInt(id), DueDate: this.state.newDate ? this.state.newDate.format("DD-MM-YYYY") : null }
this.props.apiClient.post('/api/MyEntity/Put/',
vm,
this.formPopupSucceeded,
this.formPopupFailed);
}
formPopupSucceeded() {
this.togglePopup();
window.location.reload();
}
formPopupFailed(response) {
this.togglePopup();
this.renderPopupErrors([response.Message]);
}
renderPopupErrors(validationErrors) {
this.togglePopup();
this.setState({ errors: validationErrors });
}
togglePopup() {
this.setState({
showPopup: !this.state.showPopup,
newDate: null,
});
}
render() {
return <div>
<If condition={this.state.showPopup}>
<Popup
title='My Popup Title'
closePopup={this.togglePopup}
myEntityCurrentDate={myMappedEntity.Date}
handleDateChange={this.handlePopupDateChange}
handleSubmit={this.handlePopupSubmit}
Date={this.state.newDate}
myEntityId={myMappedEntity.Id} />
</If>
</div>;
}
}
Related
I am new to React.js here. I want to pass selectedMonth state value (from Header.js) to Api create function in (App.js). There is a dropdown in the Header.js. The selectedMonth will be calculated using dropdown value. In the App.js, need to filter my data in Api.create(). Not sure how to add it using props.Can anyone help on this?
Header.js
class Header extends Component {
constructor(props) {
super(props);
this.state = {
value: ''
selectedMonth: formatMonth(defaultMonth)
};
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({selectedMonth: formatMonth(event.value) });
}
render() {
return (
<header className="header">
<Dropdown className="dropdown-month" value={this.state.value}/> '''this is the dropdown for month and the selectedMonth will be calculated from the dropdown value'''
</div>
</header>
);
}
}
export default Header;
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
};
}
getWOT() {
Api.create().getWOT().then(res => { '''want to add selectedMonth here'''
if (res.ok) {
if (res.data) {
const data = res.data.Result;
}
else {
window.alert(res.problem)
}
}
})
}
render() {
const renderusers = _.map(this.state.allWOT, (value) => (
<div className="line-bars">
<div className="bar-title">Total Reject</div>
<div className="container">
</div>
</div>
));
return (
this.state.isLoading ?
<h3 style={{ textAlign: 'center' }}>loading...</h3>
:
<div>
<Header />
<div className="grid-container">
<div className="main-grid">
</div>
{renderusers}
</div>
</div>
);
}
}
export default App;
I had moved the value and selectedMonth state to App. js.
class App extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
allWOT: [],
value: defaultMonth,
selectedMonth: formatMonth(defaultMonth),
};
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({ value: event.value, selectedMonth: formatMonth(event.value) });
}
Then, return
<Header value={this.state.value} onChange={this.onChange}/>
And in Header.js just return Dropdown with value and onChange of this.props
<Dropdown className="dropdown-month" options={months} value={this.props.value} onChange={this.props.onChange} />
My code has 2 classes which are "Forum" and PostEditor". When the button "Post" is clicked which is in the "PostEditor" class, the textarea which has the state "newPostBody" is submitted but the input which has the state "newTitle" is not submitted in ReactJs. Below there is an image of it. What am I doing wrong?
const Post = props => (
<div>
<div >{props.postBody}</div>
</div>
);
class PostEditor extends Component {
constructor(props) {
super(props);
this.state = {
newPostBody: '',
newTitle: ''
};
}
handleInputChange(ev) {
this.setState({
newPostBody: ev.target.value
});
}
handleTitleChange(ev) {
this.setState({
newTitle: ev.target.value
});
}
createPost() {
this.props.addPost(this.state.newPostBody, this.state.newTitle);
this.setState({
newPostBody: '',
newTitle: ''
});
}
render() {
return (
<div>
<div>
<input type="text" value={this.state.newTitle}
onChange={this.handleTitleChange.bind(this)}/>
<textarea value={this.state.newPostBody}
onChange={this.handleInputChange.bind(this)} />
<button onClick={this.createPost.bind(this)}
disabled={!this.state.newPostBody} > Post </button>
</div>
</div>
);
}
}
class Forum extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
};
}
addPost(newPostBody) {
const newState = Object.assign({}, this.state);
newState.posts.push(newPostBody);
this.setState(newState);
}
render() {
return (
<div>
{this.state.posts.map((postBody, idx) => {
return (
<div >
<Post key={idx} postBody={postBody} />
</div>);
})}
<PostEditor addPost={this.addPost.bind(this)} />
</div>
);
}
}
Your addPost method in Forum class is only expecting body and not expecting 2nd argument which I guess would be title:
You should update it with something like below:
class Forum extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
};
}
addPost(newPostBody, newPostTitle) {
const newState = Object.assign({}, this.state);
let post = {body: newPostBody, title: newPostTitle}
newState.posts.push(post);
this.setState(newState);
}
render() {
return (
<div>
{this.state.posts.map((post, idx) => {
return (
<div >
<Post key={idx} postBody={post.body} postTitle={post.title} />
</div>);
})}
<PostEditor addPost={this.addPost.bind(this)} />
</div>
);
}
}
Post.js
const Post = props => (
<div>
<div> {props.postTitle} </div>
<div> {props.postBody} </div>
</div>
);
it looks like in your addPost function after clicking the Post button, you are not receiving the same params as the arguments you provided from the createPost:
createPost() {
this.props.addPost(this.state.newPostBody, this.state.newTitle); <== 2 args
this.setState({
newPostBody: '',
newTitle: ''
});
}
vs
addPost(newPostBody) { <== 1 arg only
const newState = Object.assign({}, this.state);
newState.posts.push(newPostBody);
this.setState(newState);
}
I think you want to make the call to addPost look like: this.props.addPost({body: this.state.newPostBody, title: this.state.newTitle})
Then you will have to update the Post UI to handle both title and body values.
This may seem kind of basic but I'm just learning how to use React. Currently what I have going is when I type in the input field and submit, the system console logs my 'search' input. What I'm trying to do is pass my 'search' data from my child component to the parent. Looking for any tips or leads to the right direction.
This is what I have for my child component:
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
onChange = event => {
this.setState({ search: event.target.value });
};
onSubmit = event => {
const { search } = this.state;
event.preventDefault();
console.log(search);
};
render() {
return (
<div className='search-bar'>
<form onSubmit={this.onSubmit}>
<input
className='search'
type='text'
placeholder='Search'
onChange={this.onChange}
search={this.props.search}
value={this.state.searchinput}
parentCallback={this.onChange}
></input>
</form>
<FontAwesomeIcon className='search-icon' icon={faSearch} />
</div>
);
}
}
And in my Parent component (nothing much at the moment)
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
searchUpdate = search => {
console.log(search);
};
render() {
console.log(this.props.search);
return (
<div className='container'>
<SearchBar/>
</div>
);
}
}
Generally to pass data from child component to Parent Component, you can pass a reference of a function as props to child component from parent component and call that passed function from child component with data.
You can do something like this:
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
onChange = event => {
this.setState({ search: event.target.value });
};
onSubmit = event => {
const { search } = this.state;
event.preventDefault();
console.log(search);
this.props.passSearchData(search);
};
render() {
return (
<div className='search-bar'>
<form onSubmit={this.onSubmit}>
<input
className='search'
type='text'
placeholder='Search'
onChange={this.onChange}
search={this.props.search}
value={this.state.searchinput}
parentCallback={this.onChange}
></input>
</form>
<FontAwesomeIcon className='search-icon' icon={faSearch} />
</div>
);
}
In parent component:
export default class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ''
};
}
searchUpdate = search => {
console.log(search);
this.setState({ ...state, search: search })
};
render() {
console.log(this.props.search);
return (
<div className='container'>
<SearchBar passSearchData={this.searchUpdate} />
</div>
);
}
The simplest way would be to pass a function from parent to child:
// in parent component
const setSearchValue = (search) => {
// setState to search
this.setState({search});
}
render (){
return <>
<SearchBar onsearch={this.setSearchValue} />
</>
}
// in child component
// change your searchUpdate
searchUpdate = () => {
const {onsearch} = this.state;
// function call to pass data to parent
this.props.onsearch(onsearch)
}
Just have a function that is passed as a prop to the child component. Let child component do the handle change part and pass the value back to the parent and then do whatever you want to with the value
Code sandbox: https://codesandbox.io/s/react-basic-example-vj3vl
Parent
import React from "react";
import Search from "./Search";
export default class Parent extends React.Component {
searchUpdate = search => {
console.log("in parent", search);
};
render() {
console.log(this.props.search);
return (
<div className="container">
<Search handleSearch={this.searchUpdate} />
</div>
);
}
}
Child
import React from "react";
export default class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
search: ""
};
}
onChange = event => {
this.setState({ search: event.target.value }, () => {
console.log("in child", this.state.search);
this.props.handleSearch(this.state.search);
});
};
onSubmit = event => {
const { search } = this.state;
event.preventDefault();
console.log(search);
};
render() {
return (
<div className="search-bar">
<form onSubmit={this.onSubmit}>
<input
className="search"
type="text"
placeholder="Search"
onChange={this.onChange}
search={this.props.search}
value={this.state.searchinput}
/>
</form>
</div>
);
}
}
I'm trying to render buttons to a page which when clicked render the hard-coded weather data to the page depending on the day that was clicked. The click function works fine and the buttons are rendered just as I expect them to, but when a button is clicked the Day component doesn't render.
I don't understand what I'm doing wrong since my code reaches the console.log in the click handler function. Then the handler function should render the component but for some reason it does not.
Here is my code:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import myData from "./weather.json";
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log("at last"); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
class Weather extends React.Component {
render() {
return (
<div className="weather">
<div className="weather-panel">
<DayRow />
</div>
<div className="day" />
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById("root"));
Don't return UI elements in click handler as it won't render. Just set a flag inside handler and use it to display the Day component inside your render function.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDay: false
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler() {
this.setState({
showDay: true,
});
}
renderDayComponent(day) {
let daysWeather = myData[day];
console.log("now we've reached this far"); //this console log is reached when a button is clicked.
return (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler();
}}
>
{day}
</button>
{this.state.showDay && this.renderDayComponent(day)}
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
My guess is that you are trying to render the day in the Weather compoment? To do this, you must keep some sort of state so that React knows what to render. Whenever you change the state, React will call render to re-render your compoment.
Therefore, since the state of wether or not a day is to be shown is local to the Weather component, you need to store the state there:
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { activeDay: undefined }
}
render() {
// Store the current dayData in a variable if an activeDay is chosen, it will be falsy if no day has been chosen.
const dayData = this.state.activeDay && myData[this.state.activeDay]
return (
<div className="weather">
<div className="weather-panel">
// DayRow will inform Weather when a day has been selected, i.e., clicked
<DayRow onSelected={day => this.setState({ activeDay: day })} />
</div>
<div className="day">
// This is where you are rendering the day, only if a day
// is active. I.e., dayData is truthy
{ dayData && <Day humitidy={dayData.humitidy} temperature={dayData.temperature} /> }
</div>
</div>
);
}
}
Your DayRow would simple communicate with Weather by saying which day is selected.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.renderDay = this.renderDay.bind(this)
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.props.onSelected(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay("Monday")}
{this.renderDay("Tuesday")}
{this.renderDay("Wednesday")}
{this.renderDay("Thursday")}
{this.renderDay("Friday")}
{this.renderDay("Saturday")}
{this.renderDay("Sunday")}
</div>
</div>
);
}
}
Returning JSX from your event handler will not render it. You have to do all rendering in your component's render method.
You could instead have an additional object in your state that keep track of if the day has been clicked or not, and use that state in your rendering.
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null),
showDays: {}
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
this.setState(previousState => {
const showDays = { ...previousState.showDays };
showDays[day] = !showDays[day];
return { showDays };
});
}
renderDay(day) {
let daysWeather = myData[day];
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
{this.state.showDays[day] && (
<Day
humidity={daysWeather.humidity}
temperature={daysWeather.temperature}
/>
)}
</div>
);
}
// ...
}
The returned component from handler function is being passed to onClick event. Its not getting into the DOM tree.
You can change the code as shown below.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import myData from './weather.json';
const myData =
class Day extends React.Component {
constructor(props) {
super(props);
this.state = {
description: null
};
}
render() {
console.log('at last'); //this isn't reached
return (
<div className="dayWeather">
<div className="dayWeather">Humidity {this.props.humidity}</div>
<div className="dayWeather">Temperature {this.props.temperature}</div>
</div>
);
}
}
class DayRow extends React.Component {
constructor(props) {
super(props);
this.state = {
days: Array(7).fill(null)
};
this.handler = this.handler.bind(this);
this.renderDay = this.renderDay.bind(this);
}
handler(day) {
let daysWeather = myData[day];
console.log('now we\'ve reached this far'); //this console log is reached when a button is clicked.
this.props.handler(daysWeather);
}
renderDay(day) {
return (
<div>
<button
className="day"
onClick={() => {
this.handler(day);
}}
>
{day}
</button>
</div>
);
}
render() {
return (
<div>
<div className="day-row">
{this.renderDay('Monday')}
{this.renderDay('Tuesday')}
{this.renderDay('Wednesday')}
{this.renderDay('Thursday')}
{this.renderDay('Friday')}
{this.renderDay('Saturday')}
{this.renderDay('Sunday')}
</div>
</div>
);
}
}
class Weather extends React.Component {
constructor(props){
super(props);
this.state = {
selectedDay: {}
};
this.handler = this.handler.bind(this);
}
handler(day){
this.setState({
selectedDay: day
});
}
render() {
let day = null;
if(Object.keys(this.state.selectedDay) > 0){
day = <Day
humidity={selectedDay.humidity}
temperature={selectedDay.temperature}
/>;
}
return (
<div className="weather">
<div className="weather-panel">
<DayRow onDayChange={this.handler}/>
</div>
<div className="day">
{day}
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(<Weather />, document.getElementById('root'));
I'm writing dialog component in React JS. I want to make it larger. I thought, that it could be possible with using of grid. unfortunately it isn't so. How I remember in bootstrap I passed lg-class, how this issue can be solved in office365 dialog?
My component:
export default class CalendarDialog extends Component {
static defaultProps = {
allowTextInput: false,
formatDate: (date) => {
if (date) {
return date.toDateString();
}
return '';
},
firstDayOfWeek: DayOfWeek.Sunday,
isRequired: false,
isMonthPickerVisible: true,
strings: DEFAULT_STRINGS,
borderless: false,
pickerAriaLabel: 'Calender',
};
constructor(props) {
super();
let { formatDate, value } = props;
this.state = {
selectedDate: value || new Date(),
};
}
_onSelectDate = (date) => {
console.log('state', this.state);
this.setState({
selectedDate: date,
});
}
_calendarDismissed = () => {
this._dismissDatePickerPopup();
}
render(){
let {
show,
onClose
} = this.props;
const {
firstDayOfWeek,
strings,
} = this.props;
return (
<div className="ms-Grid">
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm6">
<Dialog
isOpen={ show }
type={ DialogType.normal }
onDismiss={ onClose }
title='Version'
subText=''
isBlocking={ false }
containerClassName='ms-dialogMainOverride'
>
<VersionList/>
<DialogFooter>
<DefaultButton onClick={ onClose } text='Hey' />
<PrimaryButton onClick={ onClose } text='Save' />
<DefaultButton onClick={ onClose } text='Close' />
</DialogFooter>
</Dialog>
</div>
</div>
</div>
);
}
}
It should be enough to add a CSS rule containing
.ms-dialogMainOverride {
max-width: 600px;
}
But keep in mind not to put too much information and/or choices into the dialog and that the containerClassName property is marked in documentation as deprecated.