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};
Related
When i compile the app i get this warning in the console:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
My App.js:
import "./App.css";
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
toDoList: [],
activeItem: {
id: null,
title: "",
completed: false,
},
editing: false,
};
this.fetchTasks = this.fetchTasks.bind(this);
}
componentWillMount() {
this.fetchTasks();
}
fetchTasks() {
console.log("Fetching...");
fetch("http://127.0.0.1:8000/api/task-list/")
.then((response) => response.json())
.then((data) =>
this.setState({
toDoList: data,
})
);
}
render() {
var tasks = this.state.toDoList;
return (
<div className="container">
<div id="task-container">
<div id="form-wrapper">
<form id="form">
<div className="flex-wrapper">
<div style={{ flex: 6 }}>
<input
className="form-control"
type="text"
name="title"
placeholder="Add task"
/>
</div>
<div style={{ flex: 1 }}>
<input
className="btn btn-warning"
id="submit"
type="submit"
name="Add"
/>
</div>
</div>
</form>
</div>
<div className="list-wrapper">
{
(tasks.map = (task, index) => {
return (
<div key="{index}" className="task-wrapper flex-wrapper">
<span>{task.title}</span>
</div>
)})
}
</div>
</div>
</div>
);
}
}
export default App;
Basically i'm trying to list the items in the api list but i'm missing something. Anyone help me with it?
(tasks.map = (task, index) => {
return (
<div key="{index}" className="task-wrapper flex-wrapper">
<span>{task.title}</span>
</div>
)})
Should be:
(tasks.map(task, index) => {
return (
<div key="{index}" className="task-wrapper flex-wrapper">
<span>{task.title}</span>
</div>
)})
I have a simple login form component that when I click, would like for the form to disappear and only display my json. I am a little rusty with working with react state, and appear to have the opposite effect of what I am trying. When I click on my button event, the json I am displaying will toggle appearing and disappearing, but the form stays static. I need the form to disappear and the page to populate with my grid.
Here is my components
index.jsx
import React from 'react';
import SignUp from '../SignUp';
import Cards from '../Articles/Cards';
export default class Gecko extends React.Component {
constructor(props) {
super(props);
this.state = { requestedPostsThatWeGotFromGecko: null, }
this.clickMe = this.clickMe.bind(this)
}
clickMe = () => {
const {requestedPostsThatWeGotFromGecko} = this.state;
this.setState({ requestedPostsThatWeGotFromGecko: !requestedPostsThatWeGotFromGecko })
}
render() {
console.log(this.state);
return (
<div className='gecko'>
<SignUp login={() => this.clickMe()}/>
{this.state.requestedPostsThatWeGotFromGecko &&
<Cards />
}
</div>
);
}
}
Sign up component
import React from 'react';
export default class SignUp extends React.Component {
render() {
const onClick = () => {
this.props.login();
console.log('rich');
}
return (
<div className='sign-up'>
<table className='sign-up-form'>
<tbody>
<div class="gecko-signup__tabs"><button id="gecko-signup" data-selected="yes">Sign Up</button><button id="gecko-login" data-selected="">Log In</button></div>
<tr>
<td>
<p id="signUpFree">Sign Up for Free</p>
</td>
</tr>
<div id="inputs-section">
<tr>
<td><input id="first" placeholder="First Name*" /></td>
<td><input id="last" placeholder="Last Name*" /></td>
</tr>
</div>
<tr>
<td colSpan="2"><input placeholder="Email Address*" /></td>
</tr>
<tr>
<td colSpan="2"><input placeholder="Set A Password*" /></td>
</tr>
<tr>
<td colSpan="2"><input id="getStarted" type="submit" value="Get Started" onClick={onClick}/></td>
</tr>
</tbody>
</table>
</div>
);
}
}
CardSetup component
import React from 'react';
import SignUp from '../SignUp';
export default class Articles extends React.Component {
constructor(props) {
super(props);
this.state = {
requestedPostsThatWeGotFromGecko: [],
}
}
componentDidMount(){
const api = 'https://5d445466d823c30014771642.mockapi.io/api/v1/products';
const request = new Request(api);
// Fetch isn't browser compatible...Might should fix.
fetch(request)
.then(response => {
if (response.status === 200) {
return response.json();
} else {
throw new Error('Something went wrong on api server!');
};
}).then(response => {
this.setState({
requestedPostsThatWeGotFromGecko: response
});
})
.catch(error => {
console.error(error);
});
}
render() {
return(
<div className='articles'>
{this.state.requestedPostsThatWeGotFromGecko.map(product => {
return (
<div className='flex-grid'>
<div className="card">
<div className="overflow">
<img className='productImage' src={product.image}></img>
</div>
<div className='card-body'>
<p id='name'>{product.name}</p>
<p id='description'>{product.description}</p>
<p id='price'>{product.price} </p>
</div>
</div>
</div>
);
})
}
</div>
)}}
Final Cards component
import React from 'react';
import Articles from './CardSetup';
export default class Cards extends React.Component {
render() {
return(
<div className="cards">
<h2>Products</h2>
<div className="column">
<Articles />
</div>
<div className="column">
<Articles />
</div>
<div className="column">
<Articles />
</div>
<div className="column">
<Articles />
</div>
</div>
);
}
}
I am pretty sure that I am setting the state incorrectly somewhere along the line after I press the button. I am thinking about jquery and wanting to "hide" the element but I know that is incorrect with react. Any help is greatly appreciated.
Conditionally render Cards or Signup based on truthy/falsey value of requestedPostsThatWeGotFromGecko.
render() {
const { requestedPostsThatWeGotFromGecko } = this.state;
return (
<div className="gecko">
{requestedPostsThatWeGotFromGecko ? (
<Cards />
) : (
<SignUp login={() => this.clickMe()} />
)}
</div>
);
}
Probably this is what you want:
render() {
return (
<div className='gecko'>
{!this.state.requestedPostsThatWeGotFromGecko &&
<SignUp login={() => this.clickMe()}/>
}
{this.state.requestedPostsThatWeGotFromGecko &&
<Cards />
}
</div>
);
}
If I understood correctly, you want to toggle between the Signup form and Cards based on requestedPostsThatWeGotFromGecko state variable.
So you can do something like this in your index.jsx:
render() {
return (
<div className='gecko'>
{this.state.requestedPostsThatWeGotFromGecko ?
<Cards /> :
<SignUp login={() => this.clickMe()} />
}
</div>
);
}
All you have to do is conditionally render the SignUp page on the basis of flag requestedPostsThatWeGotFromGecko.
Note: Important thing is you have to initialize it with false and make it true on the click from the SignUp page.
constructor(props) {
super(props);
this.state = { requestedPostsThatWeGotFromGecko: false };
this.clickMe = this.clickMe.bind(this)
}
render() {
const { requestedPostsThatWeGotFromGecko } = this.state;
return (
<div className="gecko">
{requestedPostsThatWeGotFromGecko ? (
<Cards />
) : (
<SignUp login={() => this.setState({ requestedPostsThatWeGotFromGecko: true })} />
)}
</div>
);
}
I want to do a toggle for the search bar. When I clicked the searchIcon, the searchBar will show or hide. However, i need to lifting up 3 level parent and child. How can I pass the onClick to do the toggle?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activities: activities,
filteredActivities: activities,
};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
filterActivity = searchText => {
//
}
handleSearchChange = inputValue => {
//
};
render() {
const filteredActivities = this.props.filteredActivities;
return(
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} />
<SearchBar inputChanged={this.handleSearchChange} />
<Content activities={this.state.filteredActivities} />
</div>
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div className="header">
<MenuIcon />
<Title name={this.props.name} />
<SearchIcon />
</div>
);
}
}
class SearchIcon extends React.Component {
render() {
return <div className="fa fa-search searchIcon" onClick={}></div>;
}
}
onClick={this.props.onClick}
or
{...props}
Full code:
import React from "react";
import "./styles.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange = inputValue => {
console.log("test");
};
render() {
return (
<div className="notificationsFrame">
<div className="panel">
<Header name={this.props.name} onClick={this.handleSearchChange} />
</div>
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div className="header">
<SearchIcon onClick={this.props.onClick} />
</div>
);
}
}
class SearchIcon extends React.Component {
render() {
return (
<div className="fa fa-search searchIcon" {...this.props}>
XXX
</div>
);
}
}
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.
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"/>