Passing Props to Child component from Parent using cloneElement - javascript

In the code below when the checkbox is checked in AddressWrapper the Ship To input in the AddressForm should be disabled. I can not figure out why AddressWrapper cloneElement is not passing it's state to the child. I have checked out many links about this issue and as far as I can tell this should work. This is the closest How to pass props to {this.props.children} to this problem but it is using a callback from the child to the parent and I need a change in parent state to update the child. I could use a publish/subscribe to do it but I'm trying to do it the 'React' way.
class AddressForm extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "Joyce",
disableInputs: props.billToSameAsShipTo
};
this.handleBillToSameAsShipToChanged = this.handleBillToSameAsShipToChanged.bind(
this
);
}
handleBillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
handleFirstNameChanged(ev) {
this.setState({ firstName: ev.target.value });
}
render() {
return (
<form>
<div className="form-row">
<div className="col-6">
<input
type="text"
className="form-control"
placeholder="First name"
disabled={this.state.disableInputs}
value={this.state.firstName}
onChange={this.handleFirstNameChanged.bind(this)}
/>
</div>
</div>
</form>
);
}
}
class AddressFormWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
billToSameAsShipTo: true
};
this.handlebillToSameAsShipToChanged = this.handlebillToSameAsShipToChanged.bind(
this
);
}
handlebillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
render() {
const billToSameAsShipTo = () => {
if (this.props.showSameAsShipTo === true) {
return (
<span style={{ fontSize: "10pt", marginLeft: "20px" }}>
<input
type="checkbox"
checked={this.state.billToSameAsShipTo}
onChange={this.handlebillToSameAsShipToChanged}
/>
<span>Same as Ship To</span>
</span>
);
}
};
const childWithProp = React.Children.map(this.props.children, child => {
return React.cloneElement(child, { ...this.state });
});
return (
<span className="col-6">
<h3>
{this.props.title}
{billToSameAsShipTo()}
</h3>
<span>{childWithProp}</span>
</span>
);
}
}
const Checkout = () => {
return (
<div>
<br />
<br />
<div className="row">
<AddressFormWrapper title="Ship To" showSameAsShipTo={false}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
</div>
</div>
);
};

In AddressFormWrapper you map over the children and passing props with cloneElement().
As per the DOCS:
Invokes a function on every immediate child contained within children...
But take a good look who are those (immediate) children of AddressFormWrapper:
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
In this case its the span element and not AddressForm.
If you render it like this it will work as expected:
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<AddressForm />
</AddressFormWrapper>
Another thing to watch out from, in AddressForm you are setting the state:
disableInputs: props.billToSameAsShipTo
This is inside the constructor and it will only run once. So it will get the initial value but won't get changed.
Either update it in componentDidUpdate or better just use the props directly:
disabled={this.props.billToSameAsShipTo}
Here is a running example:
class AddressForm extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "Joyce",
disableInputs: props.billToSameAsShipTo
};
this.handleBillToSameAsShipToChanged = this.handleBillToSameAsShipToChanged.bind(
this
);
}
handleBillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
handleFirstNameChanged(ev) {
this.setState({ firstName: ev.target.value });
}
billToSameAsShipTo() {
if (this.props.showSameAsShipTo === true) {
return (
<span style={{ fontSize: "10pt" }}>
<input
type="checkbox"
checked={this.state.billToSameAsShipTo}
onChange={this.handleBillToSameAsShipToChanged}
/> <span>Same as Ship To</span>
</span>
);
}
}
render() {
return (
<form>
<div className="form-row">
<div className="col-6">
<input
type="text"
className="form-control"
placeholder="First name"
disabled={this.props.billToSameAsShipTo}
value={this.state.firstName}
onChange={this.handleFirstNameChanged.bind(this)}
/>
</div>
</div>
</form>
);
}
}
class AddressFormWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
billToSameAsShipTo: true
};
this.handlebillToSameAsShipToChanged = this.handlebillToSameAsShipToChanged.bind(
this
);
}
handlebillToSameAsShipToChanged() {
this.setState({ billToSameAsShipTo: !this.state.billToSameAsShipTo });
}
render() {
const billToSameAsShipTo = () => {
if (this.props.showSameAsShipTo === true) {
return (
<span style={{ fontSize: "10pt", marginLeft: "20px" }}>
<input
type="checkbox"
checked={this.state.billToSameAsShipTo}
onChange={this.handlebillToSameAsShipToChanged}
/> <span>Same as Ship To</span>
</span>
);
}
};
const childWithProp = React.Children.map(this.props.children, child => {
return React.cloneElement(child, { ...this.state });
});
return (
<span className="col-6">
<h3>
{this.props.title}
{billToSameAsShipTo()}
</h3>
<span>{childWithProp}</span>
</span>
);
}
}
const Checkout = () => {
return (
<div>
<br />
<br />
<div className="row">
<AddressFormWrapper title="Ship To" showSameAsShipTo={false}>
<span className="col-6">
<AddressForm />
</span>
</AddressFormWrapper>
<AddressFormWrapper title="Bill To" showSameAsShipTo={true}>
<AddressForm />
</AddressFormWrapper>
</div>
</div>
);
};
ReactDOM.render(<Checkout />, document.querySelector("#app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"/>

Related

onSubmit method executing on render in React with Firebase

I am trying to set a document inside a collection in Firebase FireStore.
I created a component that render a div with a button inside and then it conditionally renders a FormValidation (a form) from open state (a module for easy form validation in react).
The button is setting open to true if it is not already true.
Then the formvalidation is rendered, it has an onsubmit method, a button with type="submit" and a button that sets open to false if it isn't already false (so the formvalidation dissapears).
So far so good, everything should be working fine but for some reason, whenever I click the button to show the formvalition the onSubmit runs.
Obviosly it shouldn't run until you hit the button with type="submit"
Here's the full code:
import React from 'react'; import { ValidatorForm } from 'react-form-validator-core'; import TextInput from '../TextInput'; import { compose } from 'recompose';
import PlusIcon from '../../resources/icons/add.svg'; import PlusIconBlack from '../../resources/icons/addBlack.svg'; import ArrowIcon from '../../resources/icons/arrowDown.svg'; import BinIcon from '../../resources/icons/rubbishBin.svg';
import ButtonStyles from './button.module.css'; import ListStyles from './list.module.css'; import GameStyles from './game.module.css';
import { withAuthorization } from '../Session'; import { withFirebase } from '../Firebase';
class Games extends React.Component { constructor(props) {
super(props); }
render() {
return(
<div style={{marginBottom: '6em'}}>
<h1 style={{marginTop: '2em'}}>Listas de Videojuegos</h1>
<span style={{display: 'block', width: '100%', height: '2px', background: '#ccc', marginTop: '0.5em'}} />
<ListsContainer />
</div>
); } }
class ListsContainer extends React.Component { constructor(props) {
super(props); }
render() {
return(
<div style={{marginTop: '3em'}}>
<AddButton backgroundColor="#8489C8" icon={PlusIcon} text="Añadir lista" textColor="#fff" type="list" />
<List img="https://i.pinimg.com/originals/30/0e/58/300e58c8416a68dcfcf1761501348243.jpg" backgroundColor="#6168B8" name="Lista 1" games="5" />
<List img="https://i.pinimg.com/originals/30/0e/58/300e58c8416a68dcfcf1761501348243.jpg" backgroundColor="#6168B8" name="Lista 2" games="26" />
<List img="https://i.pinimg.com/originals/30/0e/58/300e58c8416a68dcfcf1761501348243.jpg" backgroundColor="#6168B8" name="Lista 1" games="5" />
</div>
); } }
class List extends React.Component { constructor(props) {
super(props);
this.state = {
open: false,
};
this.openGames = this.openGames.bind(this); }
openGames() {
if(this.state.open === true) {
this.setState({
open: false
});
} else {
this.setState({
open: true
});
} }
render() {
const open = this.state.open;
return(
<div class={ListStyles.list} style={{background: this.props.backgroundColor}}>
<img className={ListStyles.img} src={this.props.img} />
<div className={ListStyles.textBox}>
<h2>{this.props.name}</h2>
<span><b>{this.props.games}</b> Juegos</span>
</div>
<button className={open ? ListStyles.collapseUp : ListStyles.collapse} onClick={this.openGames}><img src={ArrowIcon} alt="" /></button>
<button className={ListStyles.bin} onClick={this.openGames}><img src={BinIcon} alt="" /></button>
<div style={{marginTop: '3em'}} className={open ? ListStyles.games : ListStyles.gamesNotOpen}>
<Game name="League Of Legends" platform="PC" hours="2000" />
<Game name="Borderlands" platform="XBOX 360" hours="50" />
<span style={{display: 'block', height: '1em'}} />
<AddButton backgroundColor="#fff" icon={PlusIconBlack} text="Añadir juego" textColor="#363636" type="game" />
</div>
</div>
); } }
class Game extends React.Component { constructor(props) {
super(props); }
render() {
return(
<div class={GameStyles.gameBox}>
<h3>{this.props.name}</h3>
<span><b>{this.props.hours}</b> Horas jugadas</span>
<span><b>{this.props.platform}</b></span>
<button className={GameStyles.bin} ><img src={BinIcon} alt="" /></button>
</div>
); } }
class AddButtonBase extends React.Component { constructor(props) {
super(props);
this.state = {
open: false,
listName: '',
imgUrl: '',
gameName: '',
platform: '',
hours: ''
};
this.openForm = this.openForm.bind(this);
this.closeForm = this.closeForm.bind(this);
this.openFormList = this.openFormList.bind(this);
this.openFormGame = this.openFormGame.bind(this); }
submitList = (authUser) => {
console.log(authUser)
const { listName, imgUrl } = this.state;
this.props.firebase.list(listName, JSON.parse(authUser).uid).set(
{
imgUrl,
},
{ merge: true },
).then(() => this.closeList())
.catch(error => console.log(error.message)); }
submitGame = event => {
//const { gameName, platform, hours } = this.state;
console.log("SASNJKAB") }
handleChangeList = event => {
this.setState({ [event.target.name]: event.target.value }); };
handleChangeGame = event => {
this.setState({ [event.target.name]: event.target.value }); };
openForm() {
if(this.state.open === false) {
this.setState({
open: true
});
} }
closeForm() {
if(this.state.open === true) {
this.setState({
open: false
});
} }
openFormList = () => (
<ValidatorForm className={ButtonStyles.formList} ref="loginForm" onSubmit={this.submitList(localStorage.getItem("authUser"))}>
<h3>Añadir Lista</h3>
<span style={{display: 'block', width: '100%', height: '2px', background: '#fff', marginTop: '0.5em', marginBottom: '1.5em'}} />
<TextInput
style={{width: '26em'}}
type="text"
name="listName"
title="Nombre de la lista"
onChange={this.handleChangeList}
value={this.state.listName}
validators={['required', 'maxStringLength:20']}
errorMessages={['Campo obligatorio', 'Se ha excedido el límite de caracteres']} /><br />
<TextInput
style={{width: '26em'}}
type="text"
name="imgUrl"
title="Url para el icono de la lista"
onChange={this.handleChangeList}
value={this.state.imgUrl}
validators={['required']}
errorMessages={['Campo obligatorio']} /><br />
<div style={{textAlign: 'right'}}>
<button type="submit" className={ButtonStyles.createList}>Crear nueva lista</button>
<button className={ButtonStyles.closeList} onClick={this.closeForm}>Cerrar</button>
</div>
</ValidatorForm> )
openFormGame = () => (
<ValidatorForm className={ButtonStyles.formGame} ref="loginForm" onSubmit={this.submitGame()}>
<h3>Añadir Juego</h3>
<span style={{display: 'block', width: '100%', height: '2px', background: '#fff', marginTop: '0.5em', marginBottom: '1.5em'}} />
<TextInput
style={{width: '26em'}}
type="text"
name="gameName"
title="Nombre del videojuego"
onChange={this.handleChangeList}
value={this.state.gameName}
validators={['required', 'maxStringLength:20']}
errorMessages={['Campo obligatorio', 'Se ha excedido el límite de caracteres']} /><br />
<TextInput
style={{width: '26em'}}
type="text"
name="platform"
title="Plataforma"
onChange={this.handleChangeList}
value={this.state.platform}
validators={['required', 'maxStringLength:10']}
errorMessages={['Campo obligatorio', 'Se ha excedido el límite de caracteres']} /><br />
<TextInput
style={{width: '26em'}}
type="text"
name="hours"
title="Horas jugadas"
onChange={this.handleChangeList}
value={this.state.hours}
validators={['required', 'isNumber']}
errorMessages={['Campo obligatorio', 'Este campo sólo admite números']} /><br />
<div style={{textAlign: 'right'}}>
<button type="submit" className={ButtonStyles.createGame}>Crear nueva lista</button>
<button className={ButtonStyles.closeGame} onClick={this.closeForm}>Cerrar</button>
</div>
</ValidatorForm> )
render() {
return(
<div>
<button onClick={this.openForm} className={ButtonStyles.button} style={{ background: this.props.backgroundColor, color: this.props.textColor }}><img src={this.props.icon} /> <span>{this.props.text}</span></button>
{
(this.state.open && (this.props.type === "list")) ? this.openFormList() : null
}
{
this.state.open && this.props.type === "game" ? this.openFormGame() : null
}
</div>
); } }
const AddButton = withFirebase(AddButtonBase);
const condition = authUser => !!authUser;
export default withAuthorization(condition)(Games);
I believe your issue is that you are calling your submission methods when you are assigning them as callbacks.
You are calling this method within the brackets:
onSubmit={this.submitList(localStorage.getItem("authUser"))}
Here as well:
onSubmit={this.submitGame()}
Compare with your onclick handlers:
onClick={this.closeForm}
There are a few ways you can fix this, such as by removing the calling parentheses, or by wrapping them in anonymous functions/closures, for example:
onSubmit={()=> this.submitList(localStorage.getItem("authUser"))}

How to filter data using react?

I have created search filter but I am not able to type anything in search input why so ? I have created searchTermChanged method but why is it not working ? When user types in input field the projects should get filtered based on title.
Code:
import Projects from '../../data/projects';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
projects: Projects
}
}
searchTermChanged = (event) => {
this.setState({ projects: this.state.projects.filter(val =>
val.title.toLowerCase().indexOf(this.state.search.toLowerCase()) > -1 )
})
}
render() {
return (
<div>
<div className="header">
<div className="md-form mt-0 customsearch">
<input className="form-control" type="text" placeholder="Search projects" aria-label="Search"
value={this.state.search}
onChange={e => this.searchTermChanged(e.target.value)}
/>
</div>
</div>
<div class="container-fluid">
<div class="row">
{this.state.projects.map((val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
}
You need to make sure you're making correct use of the state.
import Projects from '../../data/projects';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
projects: Projects
}
}
searchTermChanged = (search) => {
this.setState({
//Update the search state here.
search,
//Use the current search state to filter
projects: this.state.projects.filter(val =>
val.title.toLowerCase().indexOf(search.toLowerCase()) > -1 )
}
);
}
render() {
return (
<div>
<div className="header">
<div className="md-form mt-0 customsearch">
<input className="form-control" type="text" placeholder="Search projects" aria-label="Search"
value={this.state.search}
onChange={e => this.searchTermChanged(e.target.value)}
/>
</div>
</div>
<div class="container-fluid">
<div class="row">
{this.state.projects.map((val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
}
I think if you don't need to change the projects you can also do the bellow to simplify your logic:
constructor(props) {
super(props);
this.state = {
search: ''
}
}
render() {
let {search} from this.state;
let myProjects = projects.filter((p) => {
p.title.toLowerCase().indexOf(search.toLowerCase) > -1
});
return (
<div>
<div className="header">
<div className="md-form mt-0 customsearch">
<input className="form-control" type="text" placeholder="Search projects" aria-label="Search"
value={this.state.search}
onChange={e => this.setState({search: e.target.value})}
/>
</div>
</div>
<div class="container-fluid">
<div class="row">
{myProjects.map((val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
You need to user Projects variable directly to filter otherwise filter changes will search on existing state. You need to set search value to refect what is your input
searchTermChanged = (event) => {
console.log(event);
this.setState({
projects: Projects.filter(val =>
val.title.toLowerCase().indexOf(event.toLowerCase()) > -1 ),
search: event <-- here
})
}
stackblitz: https://stackblitz.com/edit/react-fyf7fr
You are not changing the state of "search".
Assuming u have an input like this:
<input type="text" id="whatever" className="whatever" onChange={(event) => props.searchTermChanged(e.target.value)} />
you can change your method searchTermChanged
searchTermChanged = (value) => {
this.setState({search: value});
this.setState({ projects: this.state.projects.filter(val =>
val.title.toLowerCase().indexOf(value.toLowerCase()) > -1 )
});
}
The reason why u use "value" instead of "this.state.search" here "indexOf(value.toLowerCase())" its because setState is asynchronous and you can reach that piece of code with state outdated. And you are sure that "value" has the right value.

Style not updating in component - React [duplicate]

This question already has an answer here:
Update state values with props change in reactjs
(1 answer)
Closed 4 years ago.
I have a main component, and when I pass down a prop to another component, it doesn't update the style. The display is still none, whereas it's meant to update to block since I have changed the prop to true. What might be wrong?
class Apps extends Component {
constructor(props) {
super(props);
// Don't do this!
this.state = { showing: true, Login: false, Signup: false, Members: false };
}
render() {
return (
<div>
<div
className="container"
style={{ display: this.state.showing ? "block" : "none" }}
>
<div>A Single Page web application made with react</div>
</div>
<LoginComponent view={this.state.Login} />
<div className="buttons">
<a href="" ref="login" onClick={this.Login_onclick.bind(this)}>
{this.state.Login ? "back" : "Login"}
</a>
<br />
</div>
</div>
);
}
Login_onclick(e) {
this.setState({ Login: !this.state.Login });
e.preventDefault(); //alert(e.target.value);
this.setState({ showing: !this.state.showing });
// this.setState({ref: !ref});
}
}
Login Component
class LoginComponent extends Component {
constructor(props) {
super(props);
this.state = {
show: this.props.view
};
}
render() {
return (
<div
className="login"
style={{ display: this.state.show ? "block" : "none" }}
>
<h3>Login</h3>
<br />
Username: <input type="text" ref="username" />
<br />
Password <input type="password" ref="password" />
<button value="Login">Login</button>
</div>
);
}
}
You are setting this.state = { show: this.props.view }; when the component is created. Changing the view prop after that will have no effect.
There is no need for you to set show in your state if your want it to update when the prop updates.
class LoginComponent extends Component {
render() {
return (
<div className="login" style={{ display: (this.props.view ? 'block' : 'none') }}>
<h3>Login</h3><br/>
Username: <input type="text" ref="username"/><br/>
Password <input type="password" ref="password"/>
<button value="Login" >Login</button>
</div>
);
}
}

How to fix JSX props should not use .bind() Error

I have a simple component that displays data onClick event on a button. Here is my component:
import React, { Component } from 'react';
import './cardCheck.css';
class CardCheck extends Component {
constructor(props) {
super(props);
this.state = { showMessage: false };
}
_showMessage = bool => {
this.setState({
showMessage: bool
});
};
render() {
return (
<div>
<div className="newsletter-container">
<h1>Enter the ID of your card:</h1>
<div className="center">
<input type="number" />
<input type="submit" value="Check" onClick={this._showMessage.bind(null, true)} />
</div>
<div className="results" />
{this.state.showMessage && (
<div>
hello world!
<button onClick={this._showMessage.bind(null, false)}>hide</button>
</div>
)}
</div>
<h1>Offers:</h1>
</div>
);
}
}
export default CardCheck;
The code works, but I have this error in my console:
JSX props should not use .bind()
I read about it and changed my function to arrow ones like this:
import React, { Component } from 'react';
import './cardCheck.css';
class CardCheck extends Component {
constructor(props) {
super(props);
this.state = { showMessage: false };
}
_showMessage = bool => () => {
this.setState({
showMessage: bool
});
};
render() {
return (
<div>
<div className="newsletter-container">
<h1>Enter the ID of your card:</h1>
<div className="center">
<input type="number" />
<input type="submit" value="Check" onClick={this._showMessage()} />
</div>
<div className="results" />
{this.state.showMessage && (
<div>
hello world!
<button onClick={this._showMessage()}>hide</button>
</div>
)}
</div>
<h1>Offers:</h1>
</div>
);
}
}
export default CardCheck;
The error is gone, but my code does not work now. What is the correct way to do this with arrow functions and still make it work?
Either binding or using arrow function is not suggested since those functions will be recreated in every render. This is why you see those warnings. Instead of binding or invoking with an arrow function use it with reference and change your function a little bit.
_showMessage = () =>
this.setState( prevState => ( {
showMessage: !prevState.showMessage,
}) );
Instead of using a boolean, we are changing showMessage value by using its previous value. Here, we are using setState with a function to use previous state since setState itself is asynchronous.
And in your element you will use this function with its reference.
<input type="submit" value="Check" onClick={this._showMessage} />
Working example.
class CardCheck extends React.Component {
constructor(props) {
super(props);
this.state = { showMessage: false };
}
_showMessage = () =>
this.setState( prevState => ( {
showMessage: !prevState.showMessage,
}) );
render() {
return (
<div>
<div className="newsletter-container">
<h1>Enter the ID of your card:</h1>
<div className="center">
<input type="number" />
<input type="submit" value="Check" onClick={this._showMessage} />
</div>
<div className="results" />
{this.state.showMessage && (
<div>
hello world!
<button onClick={this._showMessage}>hide</button>
</div>
)}
</div>
<h1>Offers:</h1>
</div>
);
}
}
ReactDOM.render(
<CardCheck />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
<input type="submit" value="Check" onClick={this._showMessage()} />
You are invoking the _showMessage function by having the () in the onClick handler. You just want to pass the reference to the function, i.e. without ()
<input type="submit" value="Check" onClick={this._showMessage} />

Passing props from child to parent(without context)

I'm having a little bit of problem with wrapping my head around with passing states into parents. I need to send data from form container to app so that I can show updated states of list in weather info after submit
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Weather App</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<FormContainer label="Name of the city:"/>
<WeatherInfo
nameOfCity={this.state.nameOfCity}
weatherDescription={this.state.weatherDescription}
windSpeed={this.state.windSpeed}
temperature={this.state.temperature}
maxTemperature={this.state.maxTemperature}
minTemperature={this.state.minTemperature}
/>
</div>
);
}
}
export default App;
Form Container
class FormContainer extends Component {
constructor(props) {
super(props);
this.state = {
cityName: '',
nameOfCity:'',
weatherDescription:'',
windSpeed:'',
temperature:'',
maxTemperature:'',
minTemperature:''
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleCityName = this.handleCityName.bind(this);
}
handleFormSubmit(e) {
e.preventDefault();
const SendForm = {
cityName: this.state.cityName
};
console.log(SendForm);
fetch(`http://api.openweathermap.org/data/2.5/forecast/weather?q=${SendForm.cityName}&units=metric&APPID=********`)
.then(res => res.json())
.then(results => {
this.setState({
nameOfCity: results.city.name,
weatherDescription: results.list[0].weather[0].description,
windSpeed: results.list[2].wind.speed,
temperature: results.list[0].main.temp,
maxTemperature: results.list[0].main.temp_max,
minTemperature: results.list[0].main.temp_min
});
});
}
handleCityName(value) {
this.setState({ cityName: value });
}
render() {
return (
<div>
<form onSubmit={this.handleFormSubmit}>
<label>{this.props.label}</label>
<SearchBar
name="CityName"
type="text"
value={this.state.cityName}
placeholder="search"
onChange={this.handleCityName}
/>
<button type="submit"
className=""
value='Submit'
placeholder="Search" />
</form>
</div>
);
}
}
export {FormContainer};
Search bar component
const SearchBar = (props) => (
<div>
<label>{props.label}</label>
<input name={props.name} type={props.inputType} value={props.value} placeholder={props.placeholder} onChange={(e)=>props.onChange(e.target.value)}/>
</div>
);
export default SearchBar;
and Weather Info component
const WeatherInfo = (props) => (
<div>
<ul>
<li>{props.nameOfCity}</li>
<li>{props.weatherDescription}</li>
<li>{props.windSpeed}</li>
<li>{props.temperature}</li>
<li>{props.maxTemperature}</li>
<li>{props.minTemperature}</li>
</ul>
</div>
);
export default WeatherInfo;
You can pass method to update App state to FormContainer component
class App extends Component {
constructor() {
this.state = {
cityName: '',
nameOfCity:'',
weatherDescription:'',
windSpeed:'',
temperature:'',
maxTemperature:'',
minTemperature:''
};
}
updateInfo(results) {
this.setState({
nameOfCity: results.city.name,
weatherDescription: results.list[0].weather[0].description,
windSpeed: results.list[2].wind.speed,
temperature: results.list[0].main.temp,
maxTemperature: results.list[0].main.temp_max,
minTemperature: results.list[0].main.temp_min
});
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Weather App</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<FormContainer label="Name of the city:" updateInfo={this.updateInfo.bind(this)}
nameOfCity={this.state.nameOfCity}
/>
<WeatherInfo
nameOfCity={this.state.nameOfCity}
weatherDescription={this.state.weatherDescription}
windSpeed={this.state.windSpeed}
temperature={this.state.temperature}
maxTemperature={this.state.maxTemperature}
minTemperature={this.state.minTemperature}
/>
</div>
);
}
}
export default App;
And call it from FormComponent
class FormContainer extends Component {
constructor(props) {
super(props);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleCityName = this.handleCityName.bind(this);
}
handleFormSubmit(e) {
e.preventDefault();
const SendForm = {
cityName: this.props.cityName
};
console.log(SendForm);
fetch(`http://api.openweathermap.org/data/2.5/forecast/weather?q=${SendForm.cityName}&units=metric&APPID=********`)
.then(res => res.json())
.then(results => {
this.props.updateInfo(results);
});
}
handleCityName(value) {
// Do what you want to do, like resend API request or smth
}
render() {
return (
<div>
<form onSubmit={this.handleFormSubmit}>
<label>{this.props.label}</label>
<SearchBar
name="CityName"
type="text"
value={this.props.cityName}
placeholder="search"
onChange={this.handleCityName}
/>
<button type="submit"
className=""
value='Submit'
placeholder="Search" />
</form>
</div>
);
}
}
export {FormContainer};

Categories