I have a TopNab bar (component), which contains a SearchBar component. My Main component is rendering out TopNav, the main container component (Profile), and the Footer component. I want my SearchBar component to pass its state to the main container component so that the Profile component can use it.
What I am trying to build:
A user types a name into search bar and submits.
The profile matching the user name is displayed in the main container component.
Right now I have a component that can render out user profiles. I also have a component thats state updates to the user submitted value. What I need to do is pass this user submitted value to my profile component in order to render out the correct profile.
Is this possible or do I need to rebuild my components so the search is included in the Profile component?
SearchBar
import React, { Component, PropTypes } from 'react';
import Profile from './Profile';
class SearchBar extends Component {
constructor(props){
super(props)
this.state = {
name: ''
}
}
handleChange(e) {
this.setState({
name: e.target.value
});
}
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
//PASS STATE TO PROFILE COMPONENT
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit.bind(this)}>
ARMORY BETA
<input type="text" placeholder="Enter Name"
name="name"
value={this.state.name}
onChange={this.handleChange.bind(this)} />
<button className="btn btn-success" type="submit">Search</button>
</form>
</div>
)
}
}
export default SearchBar;
Profile
import React, { Component, PropTypes } from 'react';
import SearchBar from './SearchBar';
import ProfileContainer from '../containers/ProfileContainer';
class Profile extends Component {
render() {
return (
<div className="cols2">
<div>[IMG]</div>
<div>
<ProfileContainer name={this.props.name}/>
</div>
</div>
)
}
}
Profile.PropTypes = {
name: PropTypes.string
}
Profile.defaultProps = {
name: ''
}
export default Profile;
Main
import React, { Component } from 'react';
import TopNav from './TopNav';
import Footer from './Footer';
import Profile from './Profile';
class Main extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
//TopNav calls SearchBar
<TopNav />
</div>
<div className="row">
<Profile />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
export default Main;
In Main, you should add a prop to <TopNav /> that points to a handler method that will propagate the profileName state change back to Main. This, in turn, will cause Profile to be re-rendered. The handler method takes one argument profileName and is called from the handleSubmit method in TopNav. Here's the code:
SearchBar
class SearchBar extends Component {
. . .
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
this.props.handleProfileChange(profileName);
}
. . .
}
SearchBar.propTypes = {
handleProfileChange: React.PropTypes.func.isRequired,
}
Main
class Main extends Component {
constructor(props) {
super(props);
this.state = { profileName: '' }
handleProfileChange = this.handleProfileChange.bind(this);
}
handleProfileChange(profileName) {
// This state change will force Profile component to be re-rendered
this.setState( { profileName });
}
render() {
return (
<div className="container-fluid">
<div className="row">
//TopNav calls SearchBar
<TopNav handleProfileChange={this.handleProfileChange} />
</div>
<div className="row">
<Profile profileName={this.state.profileName} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
You'll need to expose a property on SearchBar that accepts a callback that will be called to indicate to its parent that the form was submitted (e.g. onSubmit)...
handleSubmit(e) {
e.preventDefault();
console.log("searching for NAME " + this.state.name);
let profileName = this.state.name;
//PASS STATE TO PROFILE COMPONENT
this.props.onSubmit(yourFormData);
}
...TopNav won't handle onSubmit itself, but just pass it on up to its own parent (perhaps renaming to "onSearchBarSubmit" along the way to make the name clearer from the perspective of TopNav's parent):
class TopNav extends Component {
render() {
return (
<div className="container-fluid">
<SearchBar onSubmit={this.props.onSearchBarSubmit}
</div>
);
}
}
class Main extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
<TopNav onSearchBarSubmit={ (criteria) => this.searchForStuff(criteria) } />
</div>
<div className="row">
<Profile data={this.state.stuffYouGotBackFromSearch} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
...OR, in some cases, it can be desirable to un-nest the components, allowing SearchBar as one of TopNav's props.children. This allows you to handle onSubmit directly within Main, and pass anything it receives onto Profile:
class Main extends Component {
render() {
return (
<div className="container-fluid">
<div className="row">
//TopNav calls SearchBar
<TopNav>
<SearchBar onSubmit={ (criteria) => this.searchForStuff(criteria) } />
</TopNav>
</div>
<div className="row">
<Profile data={this.state.stuffYouGotBackFromSearch} />
</div>
<div className="row">
<Footer />
</div>
</div>
)
}
}
...a side-benefit of un-nesting is that it would allow you to use TopNav and Searchbar independently.
Related
i'm getting the following error while trying to search. I'm using redux to hold my state. Everytime i navigate away from the following page and back to it i get the error.
"Uncaught TypeError: Cannot read properties of undefined (reading 'toLowerCase')"
the following is my insurance component
import React, { Component } from 'react';
import { connect } from "react-redux";
import { fetchAllInsurances } from '../../actions/insurance.action';
import { Link } from 'react-router-dom';
import Card from '../Card';
import SearchBar from '../SearchBar';
import Header from '../Header';
export class Insurance extends Component {
constructor(props){
super(props);
this.state = {sCode:null};
}
onSearchSubmit = (term) => {
this.setState({sCode:term})
}
componentDidMount(){
this.props.fetchAllInsurances();
}
render() {
return (
<>
<Header/>
<div>
<SearchBar onSubmit={this.onSearchSubmit}/>
</div>
<br></br>
{this.renderCreate()}
<br></br>
<br></br>
<br></br>
{this.renderCard()}
</>
)
}
renderCreate(){
return(
<>
<Link to ={'/addinsurance'} className = "ui primary button">Add Insurance</Link>
</>
)
}
renderCard(){
return(
<div className="ui four column grid">
{this.props.insurance.filter(((c)=>{
if(this.state.sCode == null){
return c;
}
else if(c.VendorName.toLowerCase().includes(this.state.sCode.toLowerCase().toString())){
return c
}
})).map((c, i) => (
<Card key={i} InsData={c} />
))}
</div>
)
}
}
const mapStateToProps = state =>{
return{
insurance:Object.values(state.insurance)
}
}
export default connect(mapStateToProps,{fetchAllInsurances})(Insurance);
the following is my search component
import React from 'react'
class SearchBar extends React.Component {
state = {term:''};
onFormSubmit =(event) =>{
event.preventDefault();
//send the state data to the parent
this.props.onSubmit(this.state.term);
}
render(){
return (
<div className='ui segment'>
<form onSubmit = {this.onFormSubmit} action="" className="ui form">
<div className="field">
<label>Insurance Vendor Search</label>
<div className="searchInputs">
<input
type="text"
value={this.state.term}
onChange={e=> this.setState({term:e.target.value})}
placeholder='Enter the insurance vendor to search...'/>
</div>
</div>
</form>
</div>
)
}
}
export default SearchBar;
As the error state, the variable is undefined. You will need to test the variable first.
So either use
c.VendorName?.toLowerCase()
or
if (c.VendorName) {
c.VendorName.toLowerCase()........
}
Any help or hint would be greatly appreciated.
In SearchBar.js, where is the "this.props" defined?
I don't see this variable being defined in the SearchBar.js class?
Is it because of React.Component class?
How can I go into this React.Component class when debugging in google chrome?
SearchBar.js:
import React from 'react';
class SearchBar extends React.Component {
state = { term: '' };
onFormSubmit = event => {
event.preventDefault();
this.props.onSubmit(this.state.term);
console.log(this.state.term);
}
render() {
return (
<div className="ui segment">
<form onSubmit={this.onFormSubmit} className="ui form">
<div className="field">
<label>Image Search</label>
<input type="text" value={this.state.term} onChange={(e) => this.setState({ term: e.target.value.toUpperCase()})} />
</div>
</form>
</div>);
}
}
App.js:
import React from 'react';
import SearchBar from './SearchBar';
class App extends React.Component {
onSearchSubmit(term) {
console.log(term);
}
render() {
return (
<div className="ui container" style={{ marginTop: '10px'}}>
<SearchBar onSubmit={this.onSearchSubmit} />
</div>
);
}
}
export default App;
export default SearchBar;
The superclass, React.Component, creates a props object on the instance and assigns to it any props passed to the instance. If you go to react.development.js (or to ReactBaseClasses.js), you'll see:
function Component(props, context, updater) {
this.props = props;
where the first argument, the props, will contain props the component was called with.
Here, since the component is invoked like this:
<SearchBar onSubmit={this.onSearchSubmit} />
It has one prop, onSubmit, so the props are:
{
onSubmit: this.onSearchSubmit
}
I want that on clicking the button X its value X pass to the result function(same with O) where I can store it in a variable. I don't know how to pass this value on calling result and access it there. I tried to find many answers but none worked.
I'm absolutely new to react. Please help!! Thanks!!
This is code snippet
import React, { Component } from 'react';
import './App.css';
import { Link } from 'react-router-dom';
class App extends Component {
render() {
return (
<div>
<h1 align="center">Welcome to TicTacToe</h1>
<br></br><br></br><br></br><br></br>
<div class="front">
Choose your sign !!
<CheckBox type='submit' value='X' id='x' onSubmit={'how to pass value'}/>
<CheckBox type='submit' value='O' id='o' onSubmit={'how to pass value'}/>
</div>
<br></br>
<Link to= "game"><p class="wrap"><button class="button">GO</button></p></Link>
{this.props.children}
</div>
);
}
}
class CheckBox extends Component{
result(i){
//what to access value
}
render(){
return (
<div className={'check-field'}>
<button type={this.props.type} value={this.props.value} name={this.props.name} id={this.props.id}>{this.props.value}</button>
</div>
);
}
}
export default App;
I'm not sure if I understand your problem properly, but to get a value from a child input component (input/button/textarea) in its parent, just pass a prop with a function which will be called in the child on any onClick/onChange callback function. Here's a little example:
class App extends React.Component {
onSubmit(value) {
// I have the button value here!
console.log(value);
}
render() {
return (
<div>
<Button value="X" onSubmit={this.onSubmit} />
<Button value="O" onSubmit={this.onSubmit} />
</div>
)
}
}
class Button extends React.Component {
onClick(event) {
const value = event.target.value;
this.props.onSubmit(value);
}
render() {
return (
<button value={this.props.value} onClick={e => this.onClick(e)}>
{this.props.value}
</button>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('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" />
I hope this solves your problem!
You can store for each checkbox value in state of App. Then, when checkbox is subimtted value it will call handleSubmit and event parameter e holds value and id of checkbox. In your case you can examine if it's X or Y and setState accordingly. This will re-render checkboxes who will via props get proper values.
import React, { Component } from 'react';
import './App.css';
import { Link } from 'react-router-dom';
class App extends Component {
state = {
X : false,
Y : false
}
handleSubmit = (e) => {
if(e.target.id === 'x') {
this.setState({X:true});
} else {
this.setState({Y:true});
}
}
render() {
const { X,Y } = this.state;
return (
<div>
<h1 align="center">Welcome to TicTacToe</h1>
<br></br><br></br><br></br><br></br>
<div class="front">
Choose your sign !!
<CheckBox type='submit' value={X} id='x' onSubmit={this.handleSubmit}/>
<CheckBox type='submit' value={Y} id='o' onSubmit={this.handleSubmit}/>
</div>
<br></br>
<Link to= "game"><p class="wrap"><button class="button">GO</button></p></Link>
{this.props.children}
</div>
);
}
}
class CheckBox extends Component{
render(){
const {value, type, name, id} = this.props;
return (
<div className={'check-field'}>
<button type={type} value={value} name={name} id={id}>{value}</button>
</div>
);
}
}
export default App;
I am really new to React, so I am trying to build a Pokemon app. My main goal is to build the list of 20 pokemon, and detail box which when clicked on Pokemon from the list should display chosen pokemon details, pictures etc.
import React, { Component } from 'react';
import './styles/App.css';
class App extends Component{
render(){
return <div className="App">
<h1> Pokedex! </h1>
<PokemonList/>
<PokemonDetail/>
</div>;
}
}
class Pokemon extends Component {
render(){
const {pokemon,id} = this.props;
return <div className="pokemon--species">
<button className="pokemon--species--container">
<div className="pokemon--species--sprite">
<img src={`https://pokeapi.co/media/sprites/pokemon/${id}.png`} />
</div>
<div className="pokemon--species--name">{id} {pokemon.name} {pokemon.url} </div>
</button>
</div>;
}
}
class PokemonDetail extends Component {
render(){
const {pokemon, id} = this.props;
return <div className="pokemon--species">
<button className="pokemon--species--container">
<div className="pokemon--species--sprite">
<img src={`https://pokeapi.co/media/sprites/pokemon/${id}.png`} />
</div>
<div className="pokemon--species--name">{id}</div>
<p>Attack:72</p>
<p>Defense:23</p>
<p>Health:99</p>
</button>
</div>;
}
}
class PokemonList extends Component{
constructor(props){
super(props);
this.state = {
species : [],
fetched : false,
loading : false,
};
}
componentWillMount(){
this.setState({
loading : true
});
fetch('https://pokeapi.co/api/v2/pokemon?limit=20').then(res=>res.json())
.then(response=>{
this.setState({
species : response.results,
loading : true,
fetched : true
});
});
}
render(){
const {fetched, loading, species} = this.state;
let content ;
if(fetched){
content = <div className="pokemon--species--list">{
species.map((pokemon,index) => <Pokemon key={pokemon.name} id={index+1} pokemon={pokemon}/>)
}
</div>;
}else if(loading && !fetched){
content = <p> Loading ...</p>;
}
else{
content = <div/>;
}
return <div className="container">
{content}
</div>;
}
}
export default App;
I know, there is much to do there, but first I want to understand how to pass ID to pokemondetails class.
Have a look at react-router and how to pass parameters to components associated with routes. Basically, you could have two routes that would render following components PokemonList and PokemonDetail. Redirect user from the PokemonList to PokemonDetail and append pokemonId to the url ( e.g "/details/23").
After redirection 'PokemonDetail' component would be rendered and pokemonId would be available in the component.
const App = () => (
<Router>
<div>
...
<Route exact path="/" component={PokemonList}/>
<Route path="/details/:pokemonId" component={PokemonDetail}/>
</div>
</Router>
)
// access pokemonId
class PokemonDetail extends Component{
render() {
return (
<div>
<h2>{this.props.params.pokemonId}</h2>
</div>
)
}
}
I'm having trouble understanding how to pass state as props to other child components in React. In my code, you can see I've got a component that takes input and maps it to my state array, displaying part of that data in another component, that's working just fine.
But the overall goal is that when a user clicks on an item they've added to the list, React Router kicks in and changes the view to the MovieDetails component, which will have extra information they've entered, like title, date and description.
I haven't even gotten to setting up react router because I can't seem to properly access state within the MovieDetails component. And then I'm not quite sure how to display the correct MovieDetails component with router.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import './App.css';
class App extends Component {
constructor() {
super();
this.addMovie = this.addMovie.bind(this);
this.state = {
movies : []
};
}
addMovie(movie) {
let movies = this.state.movies;
movies.push(movie);
this.setState({ movies });
}
render() {
return (
<div className="wrapper">
<div className="container">
<div>
<h3 className="heading">Favorite Movies</h3>
</div>
</div>
<div>
<AddMovie addMovie={ this.addMovie }/>
<MovieList movies={ this.state.movies }/>
</div>
</div>
)
}
}
class AddMovie extends Component {
addMovie(event) {
event.preventDefault();
const movie = {
title : this.title.value,
year : this.year.value,
image : this.image.value,
desc : this.desc.value
}
this.props.addMovie(movie);
this.movieForm.reset();
}
render() {
return (
<div className="container">
<form ref={(input) => this.movieForm = input} onSubmit={(e) => this.addMovie(e)}>
<input ref={(input) => this.title = input} className="Input" type="text" placeholder="Title"/>
<input ref={(input) => this.year = input} className="Input" type="text" placeholder="Year"/>
<textarea ref={(input) => this.desc = input} className="Input" type="text" placeholder="Description"></textarea>
<input ref={(input) => this.image = input} className="Input" type="text" placeholder="Poster URL"/>
<button type="submit">Add</button>
</form>
</div>
)
}
}
class MovieList extends Component {
render() {
return (
<div>
{ this.props.movies.map( (movie, i) => <MovieListItem key={i} details={ movie }/> )}
</div>
);
}
}
class MovieListItem extends Component {
constructor(props) {
super(props);
this.toggleClass = this.toggleClass.bind(this);
this.state = {
active: false
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
const { details } = this.props;
return (
<div
className={this.state.active ? "red": null}
onClick={this.toggleClass}
>
<img src={details.image} alt=""/>
<hr/>
</div>
)
}
}
class MovieDetails extends Component {
render() {
return (
<div>
<p>title here</p>
<p>year here</p>
<p>description here</p>
<img src="image" alt=""/>
</div>
)
}
}
export default App;
The problem come from the way you try to access the input values. When you use ref, you get a React wrapper, not the real DOM element, so you can't access directly to .value or .reset(). You have to use the getDOMNode() method to get the DOM element. This worked for me :
const movie = {
title : this.title.getDOMNode().value,
year : this.year.getDOMNode().value,
image : this.image.getDOMNode().value,
desc : this.desc.getDOMNode().value
};
...
this.movieForm.getDOMNode().reset();
An other thing, when you setState something that uses the current state, you should use the callback instead :
addMovie(newMovie) {
this.setState(({movies: prevMovies})=> ({
movies: [...prevMovies, newMovie]
}));
}
See complete setState API from official doc
If I got it right, do you want to push to a new component (where the details should be accessible) when you're clicking on an item created from MovieList? If so, here are the steps you have to do:
If you want to push a new view you have to use something like browserHistory or hashHistory from 'react-router'. In this case I'll use browserHistory.
To access the state in MovieDetails component simply pass it through browserHistory.
Here is the way I used your code to push to a new view when an item from MovieList component is clicked:
import {Router, Route, browserHistory} from "react-router";
class Routes extends Component {
render() {
let props = this.props;
return (
<Router history={browserHistory}>
<Route path="/" component={App}/>
<Route path="/movie-details" component={MovieDetails}/>
</Router>
)
}
}
// Here is your App component
class App extends Component {
// ... your code
}
// ... your other classes
class MovieListItem extends Component {
// ... Constructor
// Here I'm pushing the new route for MovieDetails view
toggleClass(details) {
browserHistory.push({
pathname: '/movie-details',
state: details // pass the state to MovieDetails
});
// ... your code
}
render() {
const {details} = this.props;
return (
<div
// ... your code
onClick={this.toggleClass.bind(this, details)} // pass details to toggleClass()
>
// ... your code
</div>
)
}
}
// Here is your Movie Details component
class MovieDetails extends Component {
console.log('This props: ', this.props.location.state); // The details object should be logged here
// ... your code
}
// Export Routes instead of App
export default Routes;
Hope that helps!