react.js: call a function from another component - javascript

I come from vue.js, so I'm still a bit confused about react.
I have a Message component, that shows up flash messages:
import React, {Component} from "react";
export default class Message extends Component {
constructor(props) {
super(props);
this.state = {messages: []};
}
setMessage(type, body) {
this.setState({messages: this.state.messages.concat({type: type, body: body})})
setTimeout(() => {
this.removeMessage()
}, 10000);
}
removeMessage() {
let messages = this.state.messages;
messages.shift();
this.setState({messages: messages});
}
render() {
return (
<div className="uk-messages">
{
this.state.messages.map((message, index) => {
if (message.type === 'error') {
message.type = 'danger';
}
return (
<div className={'uk-alert-' + message.type} data-uk-alert key={index}>
<p>{message.body}</p>
</div>
)
})
}
</div>
)
}
}
I use this component in my Index component:
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter as Router, Link, Route, Redirect} from 'react-router-dom'
import Auth from './helpers/auth'
import Message from './helpers/message'
class Index extends Component {
constructor(props) {
super(props);
this.state = {
authState: Auth.state,
};
Auth.initialize();
}
render() {
return (
<Router>
<div className="uk-flex uk-flex-column">
<nav className="uk-navbar-container" data-uk-navbar>
<div className="uk-navbar-left">
<a className="uk-navbar-item uk-logo" href="#"><img src={'images/logo.png'}/></a>
<ul className="uk-navbar-nav">
<li><Link to="/">Home</Link></li>
</ul>
</div>
<div className="uk-navbar-right">
<ul className="uk-navbar-nav">
<li>
<a href="#" className="uk-open" aria-expanded="true">
<span data-uk-icon="icon: user" />
</a>
</li>
</ul>
</div>
</nav>
<div className="uk-flex-grow-1">
<Route path="/" component={Welcome} />
</div>
<footer>
© 2018 stackoverflow.com
</footer>
<Message />
</div>
</Router>
);
}
}
if (document.getElementById('root')) {
ReactDOM.render(<Index/>, document.getElementById('root'));
}
Ok, so the Index component is the one I start with. I import the Message component and render it. That works. In chrome react console, I select the Message tag and can add some messages with
$r.setMessage('success','Greetings my old friend!')
The message appears. So now, how can I use the method setMessage in my Index component? In vue.js it's so simple (use $parent or $children)...

The most direct way to access the methods of another component is to pass refs, but you should probably approach the issue in a different way. Once you start tying your components together with refs, you make them less reusable and more difficult to work with.
If the list of messages were stored at a higher level, and passed into your Message component as a prop, then you would have no need for refs.
So I would recommend moving messages into the state of the Index component (or possibly even to a store), then passing messages as a prop to <Message messages={this.state.messages} />.
If you also want to manipulate the messages from within the Message component, then also pass whatever callbacks from <Index> that you need.
Below is a toy example showing the general idea of storing the state in the parent component and passing messages and addMessage as props to <Message>.
const Message = ({ messages, addMessage }) => (
<div>
{messages.map(({ text, id }) => <div key={id}>{text}</div>)}
<button onClick={addMessage}>add</button>
</div>
);
class Index extends React.Component {
constructor(props) {
super(props);
this.addMessage = this.addMessage.bind(this);
}
state = {
messages: [],
id: 0
};
addMessage() {
this.setState({
messages: [...this.state.messages, { text: "new message", id: this.state.id }],
id: this.state.id + 1
});
}
render() {
return <Message messages={this.state.messages} addMessage={this.addMessage} />
}
}
ReactDOM.render(<Index/>, document.getElementById("root"));
<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="root"></div>

You can use refs to get the reference to the element e.g.
<Message refs={(ref) => this.messageElement = ref }/>
With that you can call methods on it from anywhere after it has been assigned
this.messageElement.setMessage();

You can use refs (references) as
<Message ref={(c) => this.messageComponent = c} />
and make functions of Message component with context bound so that it is accessible in other components like
Message Component
constructor(props) {
super(props);
this.state = {messages: []};
this.setMessage = this.setMessage.bind(this);
}
Usage in Index component
this.messageComponent.setMessage('type','body');
Read more of refs here

Related

React props are updating even when i'm not using the props

I made a project using the create-react-app template. I am trying to import data from a JSON file and send it to the todos component file as props but I'm not using it as a prop in the Todos file but when I update the file using add button in-app component, it updates the todo list. I don't understand why it is doing that. It will be a great help if someone can explain what's going on.
This is the app.js file
import { Component } from 'react';
import Todos from "./todos";
import tasks from './data.json';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = { task: '' };
this.addTask = this.addTask.bind(this);
this.handleChange = this.handleChange.bind(this);
}
addTask() {
tasks.push({
title: this.state.task,
done: false
});
this.setState({ task: "" });
}
handleChange(event) {
this.setState({ task: event.target.value });
}
render() {
return (
<div className="App">
<header className="App-header">
<input className="App-task-input" type="text" placeholder="Title"
value={this.state.task} onChange={this.handleChange} />
<button className="App-add-btn" onClick={this.addTask}>Add Task</button>
</header>
<Todos tasks={tasks} />
</div>
);
}
}
export default App;
This is todos.js file
import { Component } from "react";
import tasks from "./data.json";
class Todos extends Component {
changeCheckbox(index) {
console.log(index, tasks);
}
render() {
return (
<main className="todo">
// It should update if i use this.props.tasks instead of tasks
{tasks.map((t, i) =>
<div className="todo-item" key={i}>
<input
id={t.id}
className="todo-checkbox"
type="checkbox"
checked={t.done}
onChange={this.changeCheckbox.bind(this, i)} />
<label className="todo-label" htmlFor={t.id}>{t.title}</label>
</div>
)}
</main>
);
}
}
export default Todos;
Whenever state change component re-renders and here you have parent and child component.
In your parent component you are updating your lists that's why child also getting re-render whether you are passing changes or not. I will happen.
To prevent this, you need to use shouldComponentUpdate, React.memo or PureComponent.
For your reference to understand scenario and with example.
Whenever you call method setState(), component automatically re-renders
How to prevent unnecassery re-rendering

Unable to call parent component function from child component in React

I am new to react(started this week). I have a parent and child components and I want to call a parent method in the child component. I have searched through stackoverflow and my code is same as all the solutions I got.
I have a parent component ProductDisplay which displays a list of products:
import React, { Component } from 'react';
import data from '../data'
import Product from '../Product/product.component'
class ProductDisplay extends Component {
constructor(props) {
super(props)
this.state = {
pdts: data,
}
}
addToCart = () => {
console.log('add to cart');
}
render() {
return (
this.state.pdts.map(product => (
<Product
key={product.id}
product={product}
addToCart={this.addToCart}
/>
))
);
}
}
export default ProductDisplay;
and the child component is Product which renders each product
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import "./product.css";
class Product extends Component {
constructor(props) {
super(props);
}
handleClick = () => {
this.props.addToCart();
console.log('click');
}
render() {
const product = this.props.product;
console.log(this.props.addToCart);
return (
<div className="product">
<img className="image" src={product.imgPath} alt={product.name} />
<p className="name">{product.name}</p>
<p className="price">Price: ₹{product.price}</p>
<p className="category">Category: {product.category}</p>
<button className="add">Add To Cart <i className="fa fa-cart-plus"
onClick={this.handleClick}></i></button>
</div>
);
}
}
export default withRouter(Product);
I want to call a function addToCart of ProductDisplay from Product on click of the button but it is not working. The handleClick function of the child omponent itself is not getting called. Hence the parent function which is being called from handleClick is also not getting called.
I’m also not sure if what I am doing would work in binding the method to all the buttons. Please help
You've put the onClick listener on the <i> tag, not on the actual button, which is why its not triggering anything when you click the button.
Try this instead:
<button
className="add"
onClick={this.handleClick}
>
Add To Cart <i className="fa fa-cart-plus"></i>
</button>
You need to bind the addCart method with "this" of the class. And as Chistopher's answer your onClick is on i and not on button.
Either while passing.
<Product
key={product.id}
product={product}
addToCart={this.addToCart}
/>
Or in state
this.addToCart = this.addToCart.bind(this);

Sending props two times from parent component to child component

I am getting map undefined when i am sending props Two times as separate components
import React, { Component } from 'react'
import Todo from './Todo';
export default class App extends Component {
state = {
todos: [
{id : 1 , content: "lets sleep"},
{id: 2, content:"lets eat "}
]}
deletTodo = (id) => {
console.log(id)
}
render() {
return (
<div className="App container">
<h1 className="center blue-text">Todo's</h1>
<Todo todo = {this.state.todos} />
{ <Todo deletTodo = {this.deletTodo}/> }
</div>
)
}
}
It is throwing me map of undefined but the following code does the trick i don't know why any one explain
<Todo todo = {this.state.todos} deletTodo= {this.deletTodo}/>
The following is my Todo.js where i am getting the props
import React, { Component } from 'react'
export default class Todo extends Component {
render() {
return (
<div className= "todos collection">
{
this.props.todo.map((td)=>{
return (
<div className="collection-item" key ={td.id} >
<span>{td.content}</span>
</div>
)})}
</div>
)
}
}
Both the usage of component will create seperate instances. Only the props that you provide in that instance will be available as this.props.
in <Todo todo = {this.state.todos} /> only todo prop is available and deletTodo is not available. In { <Todo deletTodo = {this.deletTodo}/> } only deletTodo is available and todos prop is not available. This is the reason you will get the error Cannot read property 'map' of undefined. You can fix this by providing a default prop so that none of the props are ever undefined.
Todo.defaultProps = {
todo: [],
deletTodo: () => null,
}
Set your state in a constructor
constructor() {
super();
this.state = {
//set state here
}

How to pass function in another file in React

I have a function that is used to change the state of a react component but I'm trying to pass the function in another file. I get the error that the function I'm trying to pass (changeView) is not defined.
This is the App.js
export default class App extends Component {
constructor() {
super();
this.state = {
language: "english",
render: ''
}
}
changeView(view, e){
console.log(view);
this.setState({render: view});
}
_renderSubComp(){
switch(this.state.render){
case 'overview': return <Overview />
case 'reviews': return <Reviews />
}
}
render() {
const {render} = this.state
return <Fragment>
<Header language={this.state.language} />
<Hero />
<Navigation render={render}/>
{this._renderSubComp()}
</Fragment>;
}
}
I'm trying to pass the changeView method to the Navigation.JS component, so I can change the active link as well as render the components listed in the _renderSubComp method above.
import React from "react";
import "./navigation.css";
import { changeView } from "../app";
export default function Navigation() {
return <div className="navigation">
<a onClick={this.changeView.bind(this,
'overview')}>Overview</a>
<a>Reviews</a>
</div>;
}
How should I pass the function to another file so it's able to change the state of my component and render the component I need.
You can't import a method like that. You will pass your function like any other prop to your component and you use there.
I've changed a few things. Firstly, I define changeView function as an arrow one, so we don't need to bind it. Secondly, I pass this function to the component as a prop. Thirdly, I used this function there like:
onClick={() => props.changeView('overview')}
As you can see it is props.changeView not state.changeView
Just go through the official documentation a little bit more. You are confused about states, props and how to pass them to your components.
class App extends React.Component {
constructor() {
super();
this.state = {
language: "english",
render: ''
}
}
changeView = (view, e) => {
console.log(view);
this.setState({ render: view });
}
render() {
const { render } = this.state
return <div>
<Navigation render={render} changeView={this.changeView} />
</div>;
}
}
const Navigation = (props) => {
return <div className="navigation">
<a onClick={() => props.changeView('overview')}>Overview</a>
<a>Reviews</a>
</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<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="root"></div>

How to pass data from parent to child in react.js

I have a parent component which has 1 child. I am updating my child by passing data through props. initially, it works fine but when I click on a button and update the state using setState the child gets rendered with old values by the time setState is finished. I have solved it using componentWillReceiveProps in the child but is this the right way?
In the below code if I do setState in filterResults function it won't update the Emplist component .
import React, { Component } from 'react';
import {Search} from './search-bar'
import Emplist from './emplist'
class App extends Component {
constructor(props){
super(props);
this.emp=[{
name:'pawan',
age:12
},
{
name:'manish',
age : 11
}]
this.state={emp:this.emp};
this.filterResults=this.filterResults.bind(this);
}
filterResults(val)
{
if(this.state)
{
let filt=[];
filt.push(
this.emp.find(e=>{
return e.age==val
})
);
this.setState({emp:filt});
}
}
render() {
return (
<div className="App">
<Search filterResults={this.filterResults}/>
<Emplist emp={this.state.emp}/>
</div>
);
}
}
export default App;
EmpList Componet
import React,{Component} from 'react'
export default class Emp extends Component
{
constructor(props){
super(props);
this.emplist=this.props.emp.map(e=>{return <li>{e.name}</li>});
this.next=this.emplist;
}
componentWillReceiveProps(nextProps,nextState,prevProps,prevState,nextContext,prevContext){
// this.props.updated(this.props.empo);
this.next=nextProps.emp[0];
if(this.next)
this.emplist= nextProps.emp.map(e=>{return <li>{e.name}</li>});
}
render(){
if(!this.next)
return <div>name not found</div>
else
return (
<div>
<br/>
<p>The list is here</p>
<ul>
{this.emplist}
</ul>
</div>
)
}
}
If you want to pass from parent to child you can pass using props and if you wan t to do reverse than you can pass one function from parent to child and than use this passed function to send something back to parent.
child will look something like this
class Reciepe extends Component{
render(){
const { title, img, instructions } = this.props;
const ingredients=this.props.ingredients.map((ing,index)=>(<li key={index} >{ing}</li>));
return (
<div className='recipe-card'>
<div className='recipe-card-img'> <img src={img} alt={title}/> </div>
<div className='recipe-card-content'>
<h3 className='recipe-title'>Reciepe {title}</h3>
<ul> {ingredients} </ul>
<h4>Instructions:</h4>
<p>{instructions}</p>
</div>
</div>
)
}
}
parent will look something like this
class RecipeList extends Component{
render(){
return (
<div style={{'display':'flex'}}>
{this.props.recipes.map((item,index)=>(
<Recipe key={index}
title={item.title}
ingredients={item.ingredients}
instructions={item.instructions}
img={item.img}
/>
))}
</div>
)
}
}
The problem is that you are assigning the values to this which is not a good practice. Check where to declare variable in React here.
If you are not using the props to do any complex operations. This should work.
EmpList Componet
import React, {Component} from 'react'
export default class Emp extends Component {
constructor(props) {
super(props);
}
render() {
if (!this.next)
return <div>name not found</div>;
else
return (
<div>
<br/>
<p>The list is here</p>
<ul>
{this.props.emp && this.props.emp.map(e => <li>{e.name}</li>)}
</ul>
</div>
)
}
}
Your next and emplist class properties are directly derivable from your props and hence you don't actually need them. You could do it in the following way
import React,{Component} from 'react'
export default class Emp extends Component{
render(){
const { emp } = this.props;
if(!emp || emp.length === 1)
return <div>name not found</div>
else {
return (
<div>
<br/> <p>The list is here</p>
<ul>
{emp.map(e=>{return <li>{e.name}</li>});}
</ul>
</div>
)
}
}
}
However in cases when you do what to make really complex decisions based on props, a combination of componentWillReceiveProps and componentDidMount/componentWillMount is the right place to do it.

Categories