I'm newbie in React and trying to build a sample search filter with data from API. Unfortunately I have problem with this code.
It's get me an error ,,Cannot read property 'filter' of undefined".
It seems to me like child component doesn't get props from parent but I declared and imported this in code.
I've tried everything what I found on the internet but nothing helps. Can someone help me out with understanding what I made wrong?
Child
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Data from './Data';
class App extends Component {
constructor() {
super();
this.state = {
search : " "
};
}
updatedSearch(event) {
this.setState(
{search : event.target.value.substr(0,15)}
)
}
render () {
console.log(this.props.names)
let filterednames = this.props.names.filter(
(name) => {
return name.toLowerCase().indexOf(this.state.
search.toLowerCase()) !== -1;
}
);
return (
<div className = "App">
<h1> Users list </h1>
<Data />
<input type = "text"
placeholder = "Search by user name"
value = {this.state.search}
onChange = {this.updatedSearch.bind(this)}
/>
<ol>
{filterednames.map(name => (
<li key={name}>{name}</li>
))}
</ol>
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
export default App;
Parent
import React, { Component } from 'react';
import App from './index';
class Data extends Component {
constructor(props) {
super(props);
this.state = {
names : [],
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
//Response
.then(response => response.json())
.then(output => {
let data = output;
//names in array
let listaimion = [];
for (let index = 0; index < data.length; index++) {
listaimion.push(data[index].name)
}
this.setState({names : listaimion})
})
}
render () {
return (
<div className = "Data">
<App names = {this.state.names} />
</div>
)
}
}
export default Data;
In the parent component, App needs to be declared. Also, App looks like your entry point of your application. Seems like, you might have mixed up Child and Parent here.
Parent -
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Data from './Data';
class App extends Component() {
constructor() {
this.state = {
names : [],
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
//Response
.then(response => response.json())
.then(output => {
let data = output;
let listaimion = [];
for (let index = 0; index < data.length; index++) {
listaimion.push(data[index].name)
}
this.setState({names : listaimion});
});
}
render () {
return (
<div className = "Data">
<Data names = {this.state.names} />
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
export default App;
Child
import React, { Component } from 'react';
class Data extends Component {
constructor(props) {
super(props);
}
render() {
let filterednames = this.props.names.filter((name) => {
return name.toLowerCase().indexOf(this.state.
search.toLowerCase()) !== -1;
}
);
return (<div>{filterednames.join(',')}</div>)
}
}
The <App> component should be the parent - that is where your state should live. You would then pass this.state.names from <App> to <Data> inside the App render method. You should not import App inside Data - App should render Data.
// App.js
class App extends Component {
state = {
names: []
}
componentDidMount(){
// fetch data and when it's done use this.setState({ names: data })
}
render() {
return <Data names={this.state.names}/>
}
}
// Data.js
const Data = (props) => {
return props.names.map(() => {...your map function})
}
Related
Here I am calling the Function openProblem every time and want to re-render the list component with different prop arguments.
I have this.state and function calling but it didn't worked out.
import React, {Component} from 'react';
import List from './demoList'
import './userHome.css'
import ReactDOM from 'react-dom';
class UserHome extends Component{
constructor(props){
super(props);
this.state = {
list: [],
}
}
openProblem = (e) =>{
console.log("difficulty = "+e.target.value)
if(e.target.value==="Easy") {
this.state.list = [1,2,3,4,5]
} else if(e.target.value==="Medium") {
this.state.list = [6,7,8,9,10]
} else {
this.state.list = [11,12,13,14,15]
}
console.log("list = "+this.state.list)
ReactDOM.render(<List list = {this.state.list}/>, document.getElementById('walla'));
}
This is the List Component which I Want to render:
import React from 'react';
import ReactDOM from 'react-dom';
class List extends React.Component {
constructor(props){
super(props);
this.state = {
list: this.props.list
}
// this.ShowProblemContent = this.ShowProblemContent.bind(this);
console.log("inside demoList = ");
}
ShowProblemContent = (e) => {
ReactDOM.render((e.target.value), document.getElementById('showProblemContent'));
}
render(){
return(
<ul>
{this.state.list.map(item => (
<button value = {item} onClick = {this.ShowProblemContent} key={item}>{item}</button>
))}
</ul>
// <button>{this.state.list}</button>
);
}
}
I dont know where in your code you are calling openProblem, but in any case, React's state is immutable. You should only change the state using setState, which means openProblem should look as follows:
openProblem = e => {
console.log("difficulty = "+e.target.value)
if(e.target.value==="Easy") {
this.setState({ list: [1,2,3,4,5] })
} else if(e.target.value==="Medium") {
this.setState({ list: [6,7,8,9,10] })
} else {
this.setState({ list: [11,12,13,14,15] })
}
// setState is async, so this console.log might show outdated data.
console.log("list = "+this.state.list)
}
Read more about React's state here.
Also, I dont know why you are calling ReactDOM.render in the component. The only place where you should call this function is index.js, to render the entire app into #root. In classic React components, you just have to implement the render function, so your final component should look like this:
import React, {Component} from 'react';
import List from './demoList'
import './userHome.css'
import ReactDOM from 'react-dom';
class UserHome extends Component {
constructor(props){
super(props);
this.state = {
list: [],
}
}
openProblem = e => {
console.log("difficulty = "+e.target.value)
if(e.target.value==="Easy") {
this.setState({ list: [1,2,3,4,5] })
} else if(e.target.value==="Medium") {
this.setState({ list: [6,7,8,9,10] })
} else {
this.setState({ list: [11,12,13,14,15] })
}
// setState is async, so this console.log might show outdated data.
console.log("list = "+this.state.list)
}
render() {
return <List list={this.state.list} />
}
}
Since i am new on React JS,i tried to use map function but it gives me the following error:Uncaught TypeError: totalData.map is not a function.It seems everything is ok in the code,please provide me some feedback.Following below is my codes:
import React, { Component } from 'react';
import axios from 'axios';
export default class TotalData extends Component {
constructor() {
super();
this.state = {
totalData: [],
isfinalData: false
}
}
componentDidMount() {
axios.get('https://nepalcorona.info/api/v1/data/nepal')
.then(res => {
this.setState({
totalData: res.data,
isfinalData: true
})
})
}
render() {
console.log("final data>>", this.state);
const { totalData, isfinalData } = this.state;
let finalData = isfinalData
? totalData.map((item, deaths) => (
<div>
<p>{item.deaths}</p>
</div>
))
: <p>Isloading</p>
return (
<div>
{finalData}
</div>
)
}
}
what may be the issue on my code ?
Following below are my fetched data from API and error i got:
import React, { Component } from 'react'
import axios from 'axios'
export default class App extends Component {
constructor() {
super()
this.state = {
totalData: [],
isfinalData: false
}
}
componentDidMount() {
axios.get('https://nepalcorona.info/api/v1/data/nepal').then((res) => {
this.setState({
totalData: res.data,
isfinalData: true
})
})
}
render() {
console.log('final data>>', this.state)
const { totalData, isfinalData } = this.state
let finalData = isfinalData ? (
<div>
<p>{totalData.deaths}</p>
</div>
) : (
<p>Isloading</p>
)
return <div>{finalData}</div>
}
}
you don't need to use map because you have only one object
I'm new to react and I am trying to fetch data from an API and pass the data to a child component. I've passed the data to the state on my parent component, however, when I pass it to the child component as props it logs as an empty array. I'm sure there is something simple I am overlooking but I don't know what, my code is below
PARENT COMPONENT
import React, {Component} from 'react';
import Child from '../src/child';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
properties: []
}
}
getData = () => {
fetch('url')
.then(response => {
return response.text()
})
.then(xml => {
return new DOMParser().parseFromString(xml, "application/xml")
})
.then(data => {
const propList = data.getElementsByTagName("propertyname");
const latitude = data.getElementsByTagName("latitude");
const longitude = data.getElementsByTagName("longitude");
var allProps = [];
for (let i=0; i<propList.length; i++) {
allProps.push({
name: propList[i].textContent,
lat: parseFloat(latitude[i].textContent),
lng: parseFloat(longitude[i].textContent)
});
}
this.setState({properties: allProps});
});
}
componentDidMount = () => this.getData();
render () {
return (
<div>
<Child data={this.state.properties} />
</div>
)
}
}
export default App;
CHILD COMPONENT
import React, {Component} from 'react';
class Child extends Component {
initChild = () => {
console.log(this.props.data); // returns empty array
const properties = this.props.data.map(property => [property.name, property.lat, property.lng]);
}
componentDidMount = () => this.initChild();
render () {
return (
<div>Test</div>
)
}
}
export default Child;
Change the componentDidMount in the child to componentDidUpdate.
The componentDidMount lifecycle method is called only once in the starting. Whereas, the componentDidUpdate lifecycle method gets called whenever there is a change in the state of the application. Since api calls are asynchronous, the initChild() function is already called once before the api call's results are passed to the child.
You can use conditional rendering
import React, {Component} from 'react';
class Child extends Component {
initChild = () => {
if(this.props.data){
const properties = this.props.data.map(property => [property.name, property.lat, property.lng]);
}
}
componentDidMount = () => this.initChild();
render () {
return (
<div>Test</div>
)
}
}
export default Child;
I'm a bit new to React and Firestore and already trying to figure out what is happening for a couple of hours. I Try to make my filter function working with data which I receive from Firestore in APP.js. I pass the data {tasks, searchTerm} to DASHBOARD component. The filter worked before when using state and props, but after replacing the hard-coded data in state with firestore data, it doesn't work anymore and I get the following error when filtering the array in the DASHBOARD component:
Cannot read property 'toLowerCase' of undefined
I've tried to send the data without any filtering directly to TASKS.js and this is working correctly (all the tasks are shown). But as soon as I pass newArray to , it doesn't work anymore.
Also, when logging task.title in tasks.filter function in the DASHBOARD component, it shows all the data (with a little delay because the data is coming from Firestore)
APP.JS -
import React, { Component } from 'react';
import './App.css';
import Dashboard from './Components/Dashboard/Dashboard'
import AddTask from './Components/Tasks/Task/AddTask'
import Navbar from './Components/Navbar/Navbar'
import Searchbar from './Components/Searchbar/Searchbar'
import firebase from './Firebase';
class App extends Component {
constructor(props) {
super(props)
this.ref = firebase.firestore().collection('tasks')
this.state = {
tasks: [],
searchTerm: ""
}
this.handleLikeButton = this.handleLikeButton.bind(this)
this.handleRemoveButton = this.handleRemoveButton.bind(this)
this.addTask = this.addTask.bind(this)
this.handleFilter = this.handleFilter.bind(this)
}
componentWillMount() {
const db = firebase.firestore()
const allTasks = []
db.collection('tasks').onSnapshot(collection => {
const tasks = collection .docs.map(doc => doc.data())
this.setState({ tasks: tasks, searchTerm: "" })
})
}
handleLikeButton = (task) => (e) => {
const tasks = [...this.state.tasks]
const index = tasks.indexOf(task)
tasks[index].likes++
this.setState({
tasks: tasks
})
}
addTask = (taskName) => (e) => {
this.ref.add({
id: Math.floor(Math.random() * 100000000000000),
title: taskName,
likes: 0
})
}
handleRemoveButton = (removingTask) => (e) => {
const tasks = [...this.state.tasks]
const newTasks = tasks.filter(task => removingTask.id !== task.id)
this.setState({
tasks: newTasks
})
}
handleFilter = (searchTerm) => {
this.setState({
searchTerm: searchTerm
})
}
render() {
return (
<div className="App">
<Navbar />
<Searchbar handleFilter={this.handleFilter} />
<AddTask addTask={this.addTask} />
<Dashboard tasks={this.state.tasks} searchTerm={this.state.searchTerm} handleLikeButton={this.handleLikeButton} handleRemoveButton={this.handleRemoveButton}/>
</div>
);
}
}
export default App;
DASHBOARD.JS -
import React, { Component } from 'react'
import Tasks from '../Tasks/Tasks'
class Dashboard extends Component {
constructor(props) {
super(props)
this.filterTasks = this.filterTasks.bind(this)
}
filterTasks = () => {
const tasks = [...this.props.tasks]
const newArray = tasks.filter(task =>
task.title.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) > -1)
return (
<Tasks tasks={newArray} handleLikeButton={this.props.handleLikeButton} handleRemoveButton={this.props.handleRemoveButton} />
)
}
render() {
return (
<div>
<h2>Dashboard</h2>
{this.filterTasks()}
</div>
)
}
}
export default Dashboard
ADDTASK.JS
import React, { Component } from 'react'
class AddTask extends Component {
constructor(props) {
super(props)
this.state = {
addNewTaskFieldEmpty: true,
taskName: ""
}
this.onChangeHandler = this.onChangeHandler.bind(this)
this.disableButton = this.disableButton.bind(this)
}
onChangeHandler(e) {
this.setState({
taskName: e.target.value,
})
this.disableButton(e.target.value)
}
disableButton(taskName) {
if(taskName.length == 0) {
this.setState({addNewTaskFieldEmpty: true})
} else {
this.setState({addNewTaskFieldEmpty: false})
}
}
render() {
return (
<div>
<div className="mdc-text-field half-size">
<input className="mdc-text-field__input " onChange={this.onChangeHandler} />
<div className="mdc-line-ripple"></div>
<label className="mdc-floating-label">Task Name</label>
</div>
<a className={"btn-floating btn-large waves-effect waves-light red " + (this.state.addNewTaskFieldEmpty ? 'disabled' : '')} onClick={this.props.addTask(this.state.taskName)}><i className="material-icons">add</i></a>
</div>
)
}
}
export default AddTask
Lint your App.css for any errors.
I encountered this message. I traced it to a CSS include:
.box-table { border-color:; border: 1px solid #dbdad8; }
The missing value of border-color: caused npm run build to fail.
Interestingly, the same file contained
.submenu-button.submenu-opened:after { background:; }
which caused no problems at all.
I have a component that displays a list of movie genres generated from a Movies API. What I want is that when the user clicks on the specific genre it displays the movies from that specified genre.
My problem is that the only way I can think of doing this is to literally make a different component for each genre, make a different action creator for each genre in my Redux that does a GET request to the API for each genre, and set the Link to that component for the specified genre. That seems really time-consuming and inefficient.
Is there a way I can make the ShowGenres component display different movies depending on what genre the user clicks on in the Genre component or is the solution I thought of the only way?
Here's my Redux:
import {createStore, applyMiddleware} from "redux";
import axios from "axios";
import thunk from "redux-thunk";
export const displayGenres = () => {
return dispatch => {
axios.get("https://api.themoviedb.org/3/genre/movie/list?api_key=<api-key>&language=en-US").then(response => {
dispatch({
type: "DISPLAY_GENRES",
genres: response.data.genres
})
}).catch(err => {
console.log(err);
})
}
}
export const selectedGenre = id => {
return dispatch => {
axios.get(`https://api.themoviedb.org/3/discover/movie?api_key=<api-key>&language=en-US&include_adult=false&include_video=false&page=1&primary_release_year=2017&with_genres=9648`).then(response => {
dispatch({
type:"SELECTED_GENRE",
select: response.data.results
})
}).catch(err => {
console.log(err);
})
}
}
const reducer = (prevState = {}, action) => {
switch(action.type){
case "DISPLAY_GENRES":
return {
genres: action.genres
}
case "SELECTED_GENRE":
return {
select: action.select,
}
default:
return prevState
}
}
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
Here's my Genres component that displays all the individual Genres for the user to choose:
import React, {Component} from "react";
import {connect} from "react-redux";
import {displayGenres} from "./redux";
import {Link} from "react-router-dom";
import Navbar from "./Navbar";
class Genres extends Component {
constructor(){
super();
}
componentDidMount(){
this.props.displayGenres();
}
render(){
const mappedGenres = this.props.genres && this.props.genres.map(genre => {
return (
<div className="mappedGenres">
<Link to="/showGenres">{genre.name}</Link>
</div>
)
})
return(
<div>
<Navbar/>
<div className="genre">
{mappedGenres}
</div>
</div>
)
}
}
export default connect(state => state, {displayGenres})(Genres);
And here's my ShowGenres component where I want to display movies depending on the Genre that the user clicks on in the Genre component:
import React, {Component} from "react";
import {connect} from "react-redux";
import {selectedGenre} from "./redux";
import {displayGenres} from "./redux";
class ShowGenres extends Component {
constructor(){
super();
}
componentDidMount(){
this.props.selectedGenre(this.mappedId);
}
render(){
const mappedId = this.props.genres && this.props.genres.map(id => {
return id.id;
})
const mappedSelected = this.props.select && this.props.select.map(genre => {
return (
<div>
<h1>{genre.title}</h1>
</div>
)
})
return(
<div>
{mappedSelected}
</div>
)
}
}
export default connect(state=> state, {displayGenres, selectedGenre})(ShowGenres);
One way you can do this is to have a method that takes input and sets the state with said input. Now where you go with it from here can vary.
You can have the second component nested inside the first, pass the method as a prop, and for each movie, add an on click event listener that fires the method from props, passing the genre of the movie you clicked as a parameter. The method has to be binded to the parent before being passed down to a child
Will write up an example and post shortly.
Here's an example. There's a minor bug somewhere, but the general idea is there. I've used it before but it's been almost a year since touching react. Forgive the sloppiness
const AllMovies = {
Horror : ['Tax Season', 'Family Dinner', 'DMV Trip 3'],
Comedy : ['Pasion of the Christ', 'The Earth is Flat'],
Romance : ['Me, Myself and Bacon', 'There\'s Something About Ice Cream']
}
class Movie
{
constructor(title, genre)
{
this.genre = genre;
this.title = title;
}
}
class ChildContainer extends React.Component
{
constructor(props)
{
super(props);
}
GenerateList()
{
let output = [];
for (var genre in AllMovies)
{
let movies = AllMovies[genre];
for (let i = 0; i < movies.length; i++)
{
output.push(new Movie(movies[i], genre))
}
}
return output.map(movie => {
return(
<li onClick={() => this.props.CallParent(movie.genre)}>
{movie}
</li>
)
})
}
render()
{
return(
<ul>
{this.GenerateList()}
</ul>
)
}
}
class ParentContainer extends React.Component
{
constructor(props)
{
super(props);
this.state = {
CurrentValue : []
}
this.PropMethod = this.PropMethod.bind(this);
}
PropMethod(newValue)
{
console.log('__GENRE__ : ', newValue);
let genre = AllMovies[newValue]
this.setState({
CurrentValue : genre
})
}
render() {
let genres = this.state.CurrentValue.map(val => {
return(<li>{val}</li>)
})
return(
<section>
<ul>
{genres}
</ul>
<ChildContainer
CallParent={this.PropMethod}
/>
</section>
)
}
}
class App extends React.Component
{
constructor(props)
{
super(props);
}
render() {
return(
<section>
<ParentContainer/>
</section>
)
}
}
React.render(<App />, document.getElementById('root'));
UPDATE
It should work now :D
There's another way. Remember that all of these components are classes. You can create an instance of both and pass one function to the others props as well. This will allow you to do it without nesting them in eachother.