I am making a basic dropdown selector. I almost had it working when I realized I was setting the state in both the parent and the child so I refactored again to try to simplify it all and put most of the responsibility in one place.
My logic is in the MyDropDown component, then I have a Header component, then the Main which should render it all.
import React from 'react';
class MyDropdown extends React.Component {
render() {
let initialUsers = this.props.state.users;
let alphabetizeUsers = initialUsers
.sort((a, b) => {
return a.name > b.name;
})
.map(obj => {
return (
<option key={obj.id} value={obj.name}>
{obj.name}
</option>
);
});
return <select>{alphabetizeUsers}</select>;
}
}
export default MyDropdown;
Then I have my main component where I do the api call and pass the state into the dropdown component.
import React from 'react';
import MyDropdown from './MyDropdown';
class UserHeader extends React.Component {
state = {
users: []
};
componentDidMount() {
let initialUsers = [];
fetch('http://localhost:3000/users')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ users: data });
});
}
render() {
return <MyDropdown state={this.state} />;
}
}
export default UserHeader;
And finally my Main Component, where I want to show the value from the selected dropdown menu
import React, { Component } from 'react';
import './Main.css';
import MyDropdown from './components/MyDropdown';
import UserHeader from './components/UserHeader';
class Main extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<span className="App-title">SELECT A USER:</span>
<UserHeader />
</header>
<p className="App-intro">
I should get the dropdown value here: {this.state.user}
</p>
</div>
);
}
}
export default Main;
What I tried doing is moving the statement
I should get the dropdown value here: {this.state.policies} .
into the UserHeader component. How do I get the value selected in the child back up to its parent?
Another thing I've tried is adding a handler to the child component
onChange = e => {
this.setState({ selectedUser: e.target.value });
};
and add it to the select... but again not sure how to get this value up to the parent.
return <select onChange={this.onChange}>{alphabetizeUsers}</select>;
The easiest way to pass the value back to the parent component is through a callback.
Try defining and passing in an onChange={this.onChange} to your Main component like so your Main component becomes:
import React, { Component } from 'react';
import './Main.css';
import MyDropdown from './components/MyDropdown';
import UserHeader from './components/UserHeader';
class Main extends Component {
this.state = {
user: null,
}
constructor(props) {
super(props);
this.onChangeUser = this.onChangeUser.bind(this);
}
onChangeUser(newUser) {
this.setState({ user: newUser });
}
render() {
return (
<div className="App">
<header className="App-header">
<span className="App-title">SELECT A USER:</span>
<UserHeader onChangeUser={this.onChangeUser} />
</header>
<p className="App-intro">
I should get the dropdown value here: {this.state.user}
</p>
</div>
);
}
}
export default Main;
Now you are passing in a callback, you can do the same thing with your UserHeader component.
import React from 'react';
import MyDropdown from './MyDropdown';
class UserHeader extends React.Component {
state = {
users: []
};
componentDidMount() {
let initialUsers = [];
fetch('http://localhost:3000/users')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ users: data });
});
}
render() {
return <MyDropdown state={this.state} onChange={this.props.onChangeUser} />;
}
}
export default UserHeader;
And finally, you can now attach this callback to your <select> element.
import React from 'react';
class MyDropdown extends React.Component {
render() {
let initialUsers = this.props.state.users;
let alphabetizeUsers = initialUsers
.sort((a, b) => {
return a.name > b.name;
})
.map(obj => {
return (
<option key={obj.id} value={obj.name}>
{obj.name}
</option>
);
});
return <select onChange={(ev) => this.props.onChange(ev.target.value)}>{alphabetizeUsers}</select>;
}
}
export default MyDropdown;
By defining the onChange on your select element like this, onChange={(ev) => this.props.onChange(ev.target.value)}, you can return the value to the main component and use it in your state.
Related
I have an issue in figuring in what conditions props are not passed down by the tree. I have a Fetcher class in which I populate with "layouts", then pass it to children props, but I cannot access it from child component.
EX:
import React, { Component } from 'react'
import axios from "axios";
export default class Fetcher extends Component {
constructor(props) {
super(props)
this.state = {
layouts: [],
}
componentDidMount() {
this.getLayouts();
}
getLayouts = () => {
axios
.get("/layout")
.then((res) => {
this.setState({
layouts: res.data,
});
})
.catch((err) => console.log(err));
};
render() {
return (
this.props.children(this.state.layouts)
)
}
}
This is my Parent component on which I pass some props children:
ex:
import React, { Fragment } from "react";
import Fetcher from "./Fetcher";
class App extends Component {
<Fetcher>
{(layouts) => {
return <Fragment>
<NewLayout
layoutsList={layouts} />
</Fragment>
}}
</Fetcher>
}
import React from "react";
class NewLayout extends React.Component {
constructor(props) {
super(props)
this.state = {
layouts: [],
}}
componentDidMount() {
this.setState(() => ({
layouts: this.props.layoutList
}))
}
render() {
{ console.log(this.state.layouts) }
{ console.log(this.props.layoutList) }
return (
....
The children prop is not a function, if you want to pass a property to it you should use React.Children API with React.cloneElement:
class Fetcher extends Component {
state = {
layouts: [/*some layout values*/],
};
render() {
const children = this.props.children;
const layouts = this.state.layouts;
return React.Children.map(children, (child) =>
React.cloneElement(child, { layouts })
);
}
}
Typo my friend, looks like you pass layoutsList prop to NewLayout, but internally use layoutList.
So i'm currently working on a PokeDex using the PokeApi available online.
The code of the project is as follows:
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import "../ui/PokemonList.css";
import axios from "axios";
export const PokemonList = class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon/",
pokemon: null
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokemon: res.data["results"] });
console.log(res);
}
render() {
return <div></div>;
}
};
export const PokeList = () => {
return (
<React.Fragment>
{this.state.pokemon ? (
<section className="poke-list">
{this.state.pokemon.map(pokemon => (
<PokemonCard />
))}
</section>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
);
};
As you can see, I have declared a state in the PokemonList Component class, but then I try to call it further down within the variable PokeList. The issue is that the state is not being recognized in PokeList
(I get the error "TypeError: Cannot read property 'state' of undefined" )
How can I go about calling the state that's declared in the class above?
-------------------EDIT-------------------------------
Okay, so I realized something. I have a code for my Dashboard.js that displays my list. Code is as follows
import React, { Component } from "react";
import { PokeList } from "../pokemon/PokemonList";
export default class Dashboard extends Component {
render() {
return (
<div>
<div className="row">
<div className="col">
<PokeList />
</div>
</div>
</div>
);
}
}
When I change the code from PokeList to PokemonList. so it'd be
import React, { Component } from "react";
import { PokemonList } from "../pokemon/PokemonList";
export default class Dashboard extends Component {
render() {
return (
<div>
<div className="row">
<div className="col">
<PokemonList />
</div>
</div>
</div>
);
}
}
I think get a list of 20 pokemon from the Api from
console.log(this.state.pokemon);.
But since I'm not displaying PokeList on the dashboard, then none of the pokemon cards display.
Screenshot of console output
First of all functional components are stateless. If you need to maintain state use class components or hooks. You can't use the state of one component in another component, You have two options,
Create a parent-child relationship between those components
Use state management libraries(Redux, etc)
There's a little of confusion between your PokemonList and PokeList component. I believe that what you really are looking for is to have just one of those. If you mix the two, you can have a component that controls the view based on the state, in your case, the state is your Pokemon list.
I mixed the two here, so your render method renders "Loading Pokemon" until you get your response back from axios, then when the response is back, it gets that data, updates your state and the state update trigger a re-render.
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import axios from "axios";
class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon/",
pokemon: null
};
componentDidMount() {
axios.get(this.state.url).then(res => {
this.setState({ pokemon: res.data["results"] });
});
}
render() {
let pokemonList = <h1>Loading Pokemon</h1>;
const pokemons = this.state.pokemon;
if (pokemons) {
pokemonList = (
<section className="poke-list">
<ul>
{pokemons.map(pokemon => (
<PokemonCard pokemon={pokemon} />
))}
</ul>
</section>
);
}
return <React.Fragment>{pokemonList}</React.Fragment>;
}
}
export default PokemonList;
I also created a simple PokemonCard component where I list the result from the API, just to show you that that approach works.
import React from "react";
const pokemonCard = props => {
return (
<li key={props.pokemon.name}>
<a href={props.pokemon.url}>{props.pokemon.name}</a>
</li>
);
};
export default pokemonCard;
You can find the final code, with PokeList and PokemonList now combined into one component called PokemonList here:
Keep in mind that if your render function depends on a certain state, it's probably certain that you should have that state being managed in that component, or passed down from a parent component.
In your example, I noticed you set url inside your state. URL is really not something that will change. It's a constant,so you can easily remove that from your state and place it in a variable and just leave your pokemon list there.
For example:
const url = "https://pokeapi.co/api/v2/pokemon/";
state = {
pokemon: null
};
componentDidMount() {
axios.get(url).then(res => {
this.setState({ pokemon: res.data["results"] });
});
}
import React , { Component } from "react";
import axios from "axios";
//make it as class based component
export default class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon/",
pokemon: null
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokemon: res.data["results"] });
console.log(res);
}
render() {
//check your data here
console.log(this.state.pokemon)
{/*pass data to child*/}
return <div> <PokeList data = { this.state } /> </div>;
}
};
//export this component
export const PokeList = (props) => {
//check your data is coming or not
console.log(props.data)
//access your data from props
return (
<React.Fragment>
{props.data.pokemon ? (
<section className="poke-list">
{props.data.pokemon.map(pokemon => (
pokemon.name
))}
</section>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
);
};
You need iterate your your pokelist passing the result from your componentDidMount function to your child component as a prop , then receive your prop in the child component here it's a working codesandbox iterating your pokemon names in the pokeList child component
topNavigation.js
import React, { Component } from 'react';
import axios from 'axios';
import SubMenu from './subMenu';
class Navigation extends Component {
state = {
mainCategory: []
}
componentDidMount() {
axios.get('http://localhost:3030/topCategory')
.then(res => {
console.log(res.data.express);
this.setState({
mainCategory: res.data.express.catalogGroupView
})
})
}
render() {
const { mainCategory } = this.state;
return mainCategory.map(navList => {
return (
<ul className="header">
<li key={navList.uniqueID}> <button className="dropbtn ">{navList.name}</button>
<SubMenu below={this.props.navList.catalogGroupView}/>
</li>
</ul>
)
})
}
}
export default Navigation;
I'm new to react and trying to make an ecommerce website. I have designed the homepage. For the navigation, I have used an external api
http://149.129.128.3:3737/search/resources/store/1/categoryview/#top?depthAndLimit=-1,-1,-1,-1
and mapped the response.
If I use the below code in place of
SubMenu component it works
<ul>
{console.log(navList.catalogGroupView)}
{
navList.catalogGroupView.map(sub=> {
return <li key={sub.uniqueID}> <button>{sub.name}</button></li>
})
}
</ul>
But as per the url endpoint response there are more sub categories which I'm unable to map.
I thought of creating a separate component to display the sub menu items. But whenever I use this code it doesn't work.
Submenu.js component
import React, { Component } from 'react';
class SubMenu extends Component {
constructor(props) {
super(props);
this.state = {
subCategory: []
};
}
render() {
return subCategory.map(sub => {
return (
<ul>
<li key={sub.uniqueID}> <button>{sub.name}</button></li>
</ul>
)
})
}
}
export default SubMenu;
Can somebody please help me on this. I would be grateful. I'm not getting where I got wrong.
You're trying to access subCategory of state. Here is the working part:
import React, { Component } from 'react';
class SubMenu extends Component {
constructor(props) {
super(props);
this.state = {
subCategory: []
};
}
render() {
const { subCategory } = this.state; // you should've add this part
return subCategory.map(sub => {
return (
<ul>
<li key={sub.uniqueID}> <button>{sub.name}</button></li>
</ul>
)
})
}
}
export default SubMenu;
UPDATE
Over here:
<SubMenu below={this.props.navList.catalogGroupView}/>
You're trying to access prop, which doesn't exist, so you should do it that way:
<SubMenu below={navList.catalogGroupView}/>
And then In Submenu, you should map over the props, not state, because your state always will be empty array(because you're not setting it). And you don't need to use state in that case, because you just need to iterate over array. So here is the code of Submenu:
import React, { Component } from 'react';
class SubMenu extends Component {
render() {
const {below} = this.props;
return below.map(sub => {
return (
<ul>
<li key={sub.uniqueID}> <button>{sub.name}</button></li>
</ul>
)
})
}
}
export default SubMenu;
And theoretically working code of topNavigation.js:
import React, { Component } from 'react';
import axios from 'axios';
import SubMenu from './subMenu';
class Navigation extends Component {
state = {
mainCategory: []
}
componentDidMount() {
axios.get('http://localhost:3030/topCategory')
.then(res => {
console.log(res.data.express);
this.setState({
mainCategory: res.data.express.catalogGroupView
})
})
}
render() {
const { mainCategory } = this.state;
return mainCategory.map(navList => {
return (
<ul className="header">
<li key={navList.uniqueID}> <button className="dropbtn ">{navList.name}</button>
<SubMenu below={navList.catalogGroupView}/>
</li>
</ul>
)
})
}
}
export default Navigation;
Hope this helps.
How I can add component CountryName and component CountryCapital to component Country ?
Display lists in browser:
Russia
Moscow
France
Paris
data.js
export default [{
id: 1,
name: 'France',
capital: 'Paris',
},
{
id: 2,
name: 'Russia',
capital: 'Moscow'
}];
First component CountryName.js
import React, { Component } from 'react'
class CountryName extends Component {
render() {
const {data} = this.props;
const CountryName = data.map(country => {
return (
<div>
<h2>{country.name}</h2>
</div>
)
})
return (
<div>
{CountryName}
</div>
);
}
}
export default CountryName;
Second component CountryCapital.js
import React, { Component } from 'react'
class CountryCapital extends Component {
render() {
const {data} = this.props;
const CountryCapital = data.map(country => {
return (
<div>
<p>{country.capital}</p>
</div>
)
})
return (
<div>
{CountryCapital}
</div>
);
}
}
export default MovieDescription;
Third component Country.js
import React, { Component } from 'react'
import CountryName from './CountryName';
import CountryCapital from './CountryCapital';
class Country extends Component {
render() {
const {data} = this.props;
const country = data.map(country => {
return (
<li key = {country.id}>
</li>
)
})
return (
<ul>
{Country}
</ul>
);
}
}
export default Country;
App.js
import React, {Component} from 'react';
import Country from './components/Country';
class App extends Component {
render() {
return (
<div>
<Country data={this.props.data}/>
</div>
)
}
}
export default App;
//HTML:
<body>
<div id="root"></div>
</body>`
First of all your components are structured in a way that, it will first list down all country names one by one and then list down country capitals one by one.
Since your requirement is to display Country and its capital as a group (like listed together in each line), you don't need two components. And in your Country component, just call the newly written component to display it as a group.
Something like this will work. Combine CountryName and CountryCapital to a single component (CountryData) like below.
const {data} = this.props;
const CountryData = data.map(country => {
return (
<div>
<div><h2>{country.name}</h2></div>
<div><h2>{country.capital}</h2></div>
</div>
)
})
return (
<div>
{CountryData}
</div>
);
And in your Component Country, call this new component and pass props like:
import CountryData from './CountryData';
......
......
render() {
const {data} = this.props;
return (
<CountryData data={this.props.data} />
);
}
Hope that helps.
Error: JSX elements must be wrapped in an enclosing tag
return (
<div><h2>{country.name}</h2></div>
<div><h2>{country.capital}</h2></div>
^
)
})
I'm currently having a problem trying to get UI to render with React. I'm using information I've received from ShopifyAPI and trying to render it to my component. I'm not sure what to do. Do I need to update the state with information returned from my API? Here's my code at the moment.
ShopifyCatalog.js
import React, { Component } from 'react';
import { Link } from 'react-router'
import styles from '../styles';
import ShopProducts from './ShopProducts'
import { getAllProducts } from '../utils/shopifyHelpers';
export default class ShopCatalog extends Component {
constructor(...args){
super(...args);
this.state = {
allProducts: []
}
}
render() {
let allProducts
getAllProducts()
.then((products) => {
return allProducts = products
})
.then((allProducts) => {
allProducts.map((product) => {
<div className='col-sm-offset-1 col-sm-2'>
<Link to={'shop/${product.id}'}>
<img src={product.images[0].src} />
<h5>{product.title}</h5>
</Link>
</div>
})
})
return (
<div style={styles.productInfo}>
{allProducts}
</div>
)
}
}
I thought it might have something to do with using promises more extensively, but I'm pretty sure it's because my state isn't updating with the information that I'm grabbing from the API. I appreciate your time, thank you.
EDIT:
I've updated my code now and it looks like this
ShopCatalog.js Updated
import React, { Component } from 'react';
import { Link } from 'react-router'
import styles from '../styles';
import ShopProducts from './ShopProducts'
import { getAllProducts } from '../utils/shopifyHelpers';
export default class ShopCatalog extends Component {
constructor(...args){
super(...args);
this.state = {
allProducts: [],
listAllProducts: []
}
}
componentDidMount() {
getAllProducts()
.then((products) => {
this.setState({
allProducts: products
})
})
}
render() {
return (
<div style={styles.productInfo}>
{this.state.allProducts.map((product) => {
<h1>{product.title}</h1>
})}
</div>
)
}
}
But it's still not rendering anything from the map of my state. Is it because map is called while there is nothing in the state? How do I work around this so map get's called and returns UI? Thank you.
Put your request in the componentDidMount lifecycle method, then update your state. Your render method is returning before your request has completed.
export default class ShopCatalog extends Component {
constructor(...args){
super(...args);
this.state = {
allProducts: []
}
}
componentDidMount() {
const _this = this;
getAllProducts()
.then((products) => {
_this.setState({ allProducts: products });
});
}
render() {
return (
<div style={styles.productInfo}>
{this.state.allProducts.map((product) => {
<div className='col-sm-offset-1 col-sm-2'>
<Link to={'shop/${product.id}'}>
<img src={product.images[0].src} />
<h5>{product.title}</h5>
</Link>
</div>
})}
</div>
)
}
}
I assume something like this, not sure specifics to your case, just giving idea how this should look like.
export default class ShopCatalog extends Component {
state = {
allProducts: []
}
getAllProducts = () => {
fetch(...API).then(response => response.json()).then(products =>
this.setState({allProducts: products}));
}
componentDidMount() {
this.getAllProducts()
}
render() {
const {allProducts} = this.state;
return (
<div>
{allProducts.map((product,key) => <div key={key}>
<span>{product.title}</span>
</div>
)}
</div>
)
}
}