Passing method child's component to another 'external' component in ReactJS - reactjs

I'm new to ReactJs, coding and this is my first time posting here! So, I'm trying to build a Todo app in ReactJs. I have four components.
the first compo. is App.js - the parent one
import React, { Component } from 'react';
import TaskTodo from './TaskTodo';
import './App.css';
import TaskDisplayed from "./TaskDisplayed";
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Hey, i'm the header! </h1>
</header>
<div className="App-intro">
<TaskTodo/>
</div>
<div className="App-right">
<TaskDisplayed/>
</div>
</div>
);
}
}
export default App;
TaskTodo.js - which is the parent of the TodoItems.js
import React, {Component} from 'react';
import TodoItems from './TodoItems';
export default class TaskTodo extends Component{
constructor(props) {
super(props);
this.state = {
items: []
};
this.addItem = this.addItem.bind(this);
};
addItem(e) {
const itemArray = this.state.items;
if (this._inputElement.value !== "") {
itemArray.unshift(
{
text: this._inputElement.value,
key: Date.now()
}
);
this.setState({
items: itemArray
});
this._inputElement.value = "";
}
e.preventDefault();
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form onSubmit={this.addItem}>
<input type="text" ref={(a) => this._inputElement = a}
placeholder="Add a list">
</input>
</form>
</div>
<TodoItems entries={this.state.items}/>
</div>
);
}
}
TodoItems.js - the child of the TaskTodo.js
import React, { Component } from 'react';
class TodoItems extends Component {
constructor(props) {
super(props);
this.createTasks = this.createTasks.bind(this);
}
handleClick = (text) => {
console.log(text);
}
createTasks(item) {
return <li key={item.key}><a onClick={() => this.handleClick(item.key, item.text)} href={'#about'}>#{item.text}</a></li>
}
render() {
const todoEntries = this.props.entries;
const listItems = todoEntries.map(this.createTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
};
export default TodoItems;
What I need to do, is how I can pass the handleClick method (a child's of TaskTodo) to an 'external' component - TaskDisplayed.js; or how I can track when the user click to a listed item? Please pardon me for this unprofessional way of asking! But, I truly need to get in track with ReactJS! Thanks!
p.s. The above code I found online, so thanks for that :D!

You should define the onClick event handler in the parent component and pass it to the child as a prop.
See How to pass an event handler to a child component in React
In this case, you would want to define it in the App component since that is the parent of the two components that need to communicate.

Related

i want to access value of one child component in another child component in react

The below is App.js file
import React,{Component} from 'react'
//import logo from './logo.svg';
import './App.css'
import InputComponent from "./components/InputComponent";
import ResultComponent from "./components/ResultComponent";
class App extends Component {
render()
{
return (
<div className="App">
<InputComponent />
<ResultComponent/>
</div>
);
}
}
export default App;
The below is InputComponent
import React,{Component} from "react";
import axios from 'axios';
class InputComponent extends Component{
constructor(props) {
super(props)
this.state = {
owner : "",
repo : "",
}
}
//here event.target.value is setting value of target to this owner
ownerName = (event) => {
this.setState({
owner:event.target.value
})
}
//here event.target.value is setting value of target to this repo
repoName = (event) => {
this.setState({
repo:event.target.value
})
}
render(){
//let submit = this.props;
let {items} = this.state;
return(
<div>
<p>The current Owner is {this.state.owner} and the current Repo is {this.state.repo}</p>
<input type='text' onChange={this.ownerName} value={this.state.owner} placeholder='Enter Username' className='inputFields'/>
<br/>
<input type='text' onChange={this.repoName} value={this.state.repo} placeholder='enter Repository' className='inputFields'/>
<br/>
</div>
);
}
}
export default InputComponent;
The below is Result Component
import React,{Component} from "react"
import axios from "axios";
class ResultComponent extends Component{
constructor() {
super()
this.state = {
items: []
}
this.apiFetch=this.apiFetch.bind(this)
}
apiFetch = () => {
axios.get(`https://api.github.com/repos/${this.props.owner}/${this.props.repo}/issues`)
.then(response => {
console.log(response)
this.setState({
items:response.data,
})
})
.catch(error => {
console.log(error)
})
}
render(){
let {items} = this.state;
return(
<div className='submit'>
<button onClick={this.apiFetch}>Fetch Results</button>
<ul>
{items.map(item=>(
<li key={item.id}>
Issue-title: {item.title}
</li>
)
)}
</ul>
</div>
);
}
}
export default ResultComponent
I want to access the value of owner,repo from InputComponent in ResultComponent in my URL part
'''axios.get(https://api.github.com/repos/${this.props.owner}/${this.props.repo}/issues)'''
but not able to do so, can anyone help me what i am doing wrong. I am not able to figure out the issue I am new to React.
In general, there are the options for passing data between react components :
From Parent to Child using Props
From Child to Parent using Callbacks
Between Siblings :
(i) Combine above two methods
(ii) Using Redux
(iii) Using React’s Context API
Use design pattern like HOC or render Props for sharing code between React components (render code abstrait => good practice for reusable)
In your case, it's good pratice with the design pattern render Props. For example, I propose an example of codes :
class InputComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
owner : "",
repo : "",
}
}
//here event.target.value is setting value of target to this owner
ownerName = (event) => {
this.setState({
owner:event.target.value
})
}
//here event.target.value is setting value of target to this repo
repoName = (event) => {
this.setState({
repo:event.target.value
})
}
render() {
return (
<input type='text' onChange={this.ownerName} value={this.state.owner} placeholder='Enter Username' className='inputFields'/>
{/*
use the `render` prop to dynamically determine what to render.
*/}
{this.props.render(this.state)}
</div>
);
}
}
class WithInputComponent extends React.Component {
render() {
return (
<div>
<InputComponent render={dataInput => (
<ResultComponent dataInput={dataInput} />
)}/>
</div>
);
}
}
Here the links in more details :
https://en.reactjs.org/docs/render-props.html
https://towardsdatascience.com/passing-data-between-react-components-parent-children-siblings-a64f89e24ecf
There are three answers to this question:
You should set your state as high on the DOM tree as you can so that
you can pass the values down to siblings. In simple terms, if state
is set by the parent of the two, you can just ask for state from the
parent and you're done.
You can use a state management system like Redux, which effectively
does the same thing behind the scenes.
You can use refs, but you probably shouldn't so ignore that.
If I were you, I would just bring my state up to App.js, modify it from InputComponent, and pass that modified state down to ResultComponent.
class App extends Component {
constructor(props){
super(props)
this.state = {//initial values}
}
changeSomething() {
// function that changes your state's values
}
render()
{
return (
<div className="App">
<InputComponent aFunctionProp={changeSomething} />
<ResultComponent inputVals={this.state}/>
</div>
);
}
}
export default App;
Check this out as well:
https://reactjs.org/tutorial/tutorial.html
Remember that when you pass down props through your component, you refer to them by their prop name, not by the value you pass in. So in InputComponent, you'll be looking for aFunctionProp() rather than changeSomething(). That was pretty confusing to me when I first learned React.

React - Cannot render list item that is retrieved from API

I am writing a react application that outputs a list of books (image, title, category, and description).
My search bar and booklist are sibling components and the search bar will pass data to the booklist.
when clicking the search button, only "Sample Category" shows up but not anything else. There is no problem accessing the API and the data is not null.
Here is a sample API output: https://www.googleapis.com/books/v1/volumes?q=lordoftherings
My code is the following:
// App
import React, { Component } from 'react';
import Axios from 'axios';
import './App.css';
import SearchBar from './SearchBar';
import BookList from './BookList';
class App extends Component {
constructor(props) {
super(props);
this.state = {
books: []
};
this.search = this.search.bind(this);
}
search(title) {
const promise = Axios.get('https://www.googleapis.com/books/v1/volumes?q=' + title);
promise.then((response) => {
const books = response.data.items;
this.setState({ books: books });
console.log(this.state.books);
})
};
render() {
return (
<div className="App">
<SearchBar searchBooks = {this.search}/>
<BookList booklist = {this.state.books}/>
</div>
);
}
}
export default App;
// Search Bar
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = { titleToSearch: 'harry potter' }
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
this.setState({ titleToSearch: e.target.value });
};
render() {
return (
<form>
<input
type="text"
name="booksInput"
placeholder="Enter book title"
value={this.state.titleToSearch}
onChange={this.handleInputChange}
/>
<button type="button" onClick={() => this.props.searchBooks(this.state.titleToSearch)}>Search</button>
</form>
);
}
}
export default SearchBar;
// BookList
import React, { Component } from 'react';
class BookList extends Component {
render() {
const books = this.props.booklist;
return (
<div className="table">
{books.map((book) => {
console.log(book.id);
return (
<div className="box" key={book.id}>
<div className="img"><img src="assets/default-placeholder.jpg" alt="" /></div>
<div className="title">{book.title}</div>
<div className="category">Sample Category</div>
<div className="description">{book.description}</div>
</div>
);
})}
</div>
);
}
}
export default BookList;
In the sample code you provided, you're not actually dynamically outputting categories.
You've hard coded 'Sample category' in there.
book.category
...is not actually in the dataset.
There are categories which seem to be available under:
<div className="category">{book.volumeInfo.categories[0]}</div>
although you'll want to check if the array has length, and probably map or join each item in array to string.
just to be clear: the issue with your other fields is also that they're children of "volumeInfo"

cannot read of props null in react js

when trying to click the delete button the error is displayed stating that cannot read props of null and try to bind the method in the constructor class using bind.this but again the same error is displayed. also bind the value at the bottom of the component again the same error that cannot read value of props as null
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import SampleData from './data.js';
import _ from 'lodash';
import AppList from './Applist';
import Appointment from './Appointment';
import './App.css';
class App extends Component {
constructor(){
super()
this.state = {
data:[],
aptBodyVisible: false
}
this.deleteMessage = this.deleteMessage.bind(this);
this.toggleAddDisplay = this.toggleAddDisplay.bind(this);
}
componentDidMount(){
this.setState({data: SampleData})
}
deleteMessage(item) {
var allApts = this.state.data;
var newApts = _.without(allApts, item);
this.setState({
data: newApts
});
}
toggleAddDisplay(){
var tempVisibility = !this.state.aptBodyVisible;
this.setState({
aptBodyVisible: tempVisibility
})
}
render() {
var filtered = this.state.data;
filtered = filtered.map((item, index)=>{
return(
<AppList key = { index }
singleItem = { item }
whichItem = { item }
onDelete = {this.deleteMessage}
/>
)
})
return (
<div className="main">
<Appointment
bodyVisible = { this.state.aptBodyVisible }
handleToggle = { this.toggleAddDisplay } />
<ul className="item-list media-list">{filtered} </ul>
</div>
);
}
}
export default App;
child class component
import React, { Component } from 'react';
class AppList extends Component {
handleDelete(){
this.props.onDelete(this.props.whichItem);
}
render(){
return(
<li className="pet-item media">
<div className="media-left">
<button className="pet-delete btn btn-xs btn-danger"
onClick = {this.handleDelete}>
<span className="glyphicon glyphicon-remove"></span></button>
</div>
<div className="pet-head">
<span className="pet-name">{this.props.singleItem.petName}</span>
<span className="apt-date pull-right">{this.props.singleItem.aptDate}</span>
</div>
<div className="owner-name"><span className="label-item">Owner:</span>
{this.props.singleItem.ownerName}</div>
<div className="apt-notes">{this.props.singleItem.aptNotes}</div>
</li>
)
}
}
export default AppList;
From the React Documentation
The constructor for a React component is called before it is mounted. When implementing the constructor for a React.Component subclass, you should call super(props) before any other statement. Otherwise, this.props will be undefined in the constructor, which can lead to bugs.
Like this:
constructor(props){
super(props);
this.state = {
data:[],
aptBodyVisible: false
}
this.deleteMessage = this.deleteMessage.bind(this);
this.toggleAddDisplay = this.toggleAddDisplay.bind(this);
}
yes again we need to bind the method in the child components even to work with the click events
onClick = {this.handleDelete.bind(this)}

Passing state to more than one child component in React

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!

How can I update the state of my container component from one of its children in React?

I'm sorry if this is a frequent question but it's something I'm reallly struggling to understand.
I'm building a basic to-do list app in React.
The container currently contains a form that takes in the information, and adds each item inputted an items array in the state. I then have a 'TaskList' component that takes this state and renders my tasks.
What I want to do is create a separate form component, instead of having the form within my container.
The issue is that if I just copy the code for the form into a new component, the state it will modify is its own, and therefore won't be accessible via the TaskList component to render the list of tasks.
Is there any way to have a component that can update the state of its parent component. My source code is below for reference.
export default class Container extends React.Component {
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
items: []
}
}
handleSubmit(e) {
e.preventDefault();
var itemsArray = this.state.items;
itemsArray.push(e.target.elements.task.value);
this.setState({
items: itemsArray
})
e.target.reset();
}
render() {
return (
<div className="">
<header className="header">TODO</header>
<form onSubmit={this.handleSubmit}>
<input name="task" placeholder="Task"></input>
<button type="submit">Add</button>
</form>
<TaskList data={this.state.items} />
</div>
);
}
}
<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>
const TaskList = props => {
var tasks = (props.data).map( (item, key) => { return <Task data={item} key={key} /> })
return(
<ul className="gif-list">
{tasks}
</ul>
);
}
export default TaskList;
<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>
You can do it this way. Pass the parent function that change the state as a props to the form component. In the form handle submit function, call the parent function as this.props.addTodo(todoText).
Container.js
import React, { Component } from 'react';
import TaskList from './TaskList';
import Form from './Form';
export default class Container extends Component {
constructor() {
super();
this.handleAdd = this.handleAdd.bind(this);
this.state = {
items: []
}
}
handleAdd(todoText) {
var itemsArray = this.state.items;
itemsArray.push(todoText);
this.setState({
items: itemsArray
})
}
render() {
return (
<div className="">
<header className="header">TODO</header>
<Form addTodo={this.handleAdd}/>
<TaskList data={this.state.items} />
</div>
);
}
}
Form.js
import React, { Component } from 'react';
export default class Form extends Component {
handleSubmit(e) {
e.preventDefault();
let todoText = e.target.elements.task.value;
if(todoText.length > 0) {
e.target.elements.task.value = '';
this.props.addTodo(todoText);
}else{
e.target.elements.task.focus();
}
}
render() {
return(
<div>
<form onSubmit={(e) => this.handleSubmit(e)}>
<input name="task" placeholder="Task"></input>
<button type="submit">Add</button>
</form>
</div>
);
}
}
TaskList.js
import React from 'react';
const TaskList = props => {
var tasks = (props.data).map( (item, key) => { return <li key={key}>{item}</li> })
return(
<ul className="gif-list">
{tasks}
</ul>
);
}
export default TaskList;

Resources