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

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

Related

pushing content of textarea into an array

I have a textarea and i want to push its content into an array that I defined
I have created a Tasks component and used the onClick property to push the value of its textarea's innerhtml into the array.
App.js :
import React from 'react';
import Tasks from './tasks.js';
import './App.css';
let tasks= [
{name:""},
{name:""},
{name:""},
{name:""},
{name:""}
]
function App(props) {
return (
<div className="App">
<Tasks onClick={()=>{
tasks.push(this.props.value)
}} />
</div>
);
}
export default App;
tasks.js:
import React from 'react'
class Tasks extends React.Component {
render() {
return (<div>
<textarea value={this.props.value} ></textarea>
<button onClick={this.props.onClick}>Add task</button>
</div>)
}
}
export default Tasks
The app compiled successfully, but when I click on the button, the "Cannot read property 'props' of undefined" error appears
Maintain state in child component to store value of text-area,
constructor(props) {
super(props)
this.state = {
value: '',
}
}
You can set state value, when your text-area change,
handleChange = e => {
this.setState({value: e.target.value})
}
On the click of the button, you need to pass the value from text-area to parent component like,
<textarea value={this.state.value} onChange={this.handleChange} />
<button onClick={() => this.props.onClick(this.state.value)}>
Add task
</button>
In parent component, you can push value of text-area into your array.
function addData(val) {
tasks.push(val)
console.log(tasks)
}
<Tasks onClick={value => addData(value)} />
Demo

How do I access some data of a property I passed from parent component to child component?

I am learning React and I am trying to call a function in a child component, that accesses a property that was passed from parent component and display it.
The props receives a "todo" object that has 2 properties, one of them is text.
I have tried to display the text directly without a function, like {this.props.todo.text} but it does not appear. I also tried like the code shows, by calling a function that returns the text.
This is my App.js
import React, { Component } from "react";
import NavBar from "./components/NavBar";
import "./App.css";
import TodoList from "./components/todoList";
import TodoElement from "./components/todoElement";
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo(input) {
const newTodo = {
text: input,
done: false
};
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" />
<button
onClick={() => this.addNewTodo(document.getElementById("text"))}
>
Add new
</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
export default App;
This is my todoElement.jsx
import React, { Component } from "react";
class TodoElement extends Component {
state = {};
writeText() {
const texto = this.props.todo.text;
return texto;
}
render() {
return (
<div className="row">
<input type="checkbox" />
<p id={this.writeText()>{this.writeText()}</p>
<button>x</button>
</div>
);
}
}
export default TodoElement;
I expect that when I write in the input box, and press add, it will display the text.
From documentation
Refs provide a way to access DOM nodes or React elements created in the render method.
I'll write it as:
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.textRef = React.createRef();
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo() {
const newTodo = {
text: this.textRef.current.value,
done: false
};
const todos = [...this.state.todos, newTodo];
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" ref={this.textRef} />
<button onClick={this.addNewTodo}>Add new</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
In your approach, what you got as an argument to the parameter input of the method addNewTodo is an Element object. It is not the value you entered into the text field. To get the value, you need to call input.value. But this is approach is not we encourage in React, rather we use Ref when need to access the html native dom.

Pass value from nested react component childs to the form onSubmit handle function

I'm new with Reactjs and I'm trying to use this.refs.myComponent to get the value of an imput field, but this input field is nested in another react component. Let me share an example of what I mean:
Imagine that i have this:
class ParentComponent extends React.Component {
onFormSubmit(e) {
console.log(this.refs.childName);
}
render() {
return (
<form onSubmit={this.onFormSubmit.bind(this)}>
<ChildComponent refName='childName'/>
<button type='submit'>Submit</button>
</form>
);
}
}
class ChildComponent extends React.Component {
render() {
return (
<input type='text' ref={this.props.refValue} name={this.props.refValue} id={this.props.refValue}/>
);
}
}
The problem is when I call this.refs.childName I can't take the value as part of the form submit event without doing something like evt.target.childName.value?
Regards
Generally speaking, refs are not the preferred way to handle passing UI data (state in React) down to child components. It's better to avoid using refs when possible.
This is a great explanation of the relationship between props, and components.
And this explains state and some of the core beliefs about state within the React framework.
So here is an example to accomplish what you are trying to do, in a "React friendly" way. Your ChildComponent can be stateless, so it only has one responsibility, to render the props passed down to it, whose props handled as state in ParentComponent.
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
const ChildComponent = (props) => {
return (
<input
type='text'
value={props.textValue}
onChange={props.onTextChange}
/>
)
}
class ParentComponent extends Component {
constructor(props) {
super(props)
this.onFormSubmit = this.onFormSubmit.bind(this)
this.onTextChange = this.onTextChange.bind(this)
this.state = {
textValue: ''
}
}
onFormSubmit(e) {
e.preventDefault()
console.log(`You typed: ${this.state.textValue}`)
}
onTextChange(e) {
this.setState({textValue: e.target.value})
}
render() {
return (
<form onSubmit={this.onFormSubmit}>
<ChildComponent
textValue={this.state.textValue}
onTextChange={this.onTextChange}
/>
<button type='submit'>Submit</button>
</form>
)
}
}
// assumes you have an element with an id of 'root'
// in the root html file every React app has
ReactDOM.render(<ParentComponent/>, document.getElementById('root'))
I would strongly recommend using create-react-app to get a quick and easy React app running without doing anything.
Hope it helps.

How to input data in one React component and have it render in another?

I'm learning React using JSX and ES6 and I've got a pretty decent handle on how to create components and route to different views using ReactRouter4.
What I still haven't been able to figure out is for example how i can create an Admin page where I input the details of a work for my portfolio and have all the works render on the another page, presumably Portfolio page.
Here's what I've got.
App.js loads the Portfolio.js component
import React from 'react';
import Navigation from './Navigation';
import Title from './Title';
import Portfolio from './Portfolio';
class App extends React.Component {
render() {
return(
<div className="container-fluid">
<div className="container">
<div className="row">
<div className="col-sm-12">
<Navigation />
<Title title="kuality.io"/>
<section className="app">
<Portfolio works={this.props.works} />
</section>
</div>
</div>
</div>
</div>
)
}
}
export default App;
The Portfolio.js component has a constructor to bind a unique method named addWork(), the React methods componentWillMount() and componentWillUnmount() to handle state, and the default render(). One more thing to mention about this component is that it's calling a component called ../base which has all the details to an online DB via Firebase. So if that's relevant as to where it is place, then take that into consideration otherwise don't sweat it.
import React from 'react';
import Work from './Work';
import Admin from './Admin';
import base from '../base';
class Portfolio extends React.Component {
constructor() {
super();
this.addWork = this.addWork.bind(this);
// getInitialState
this.state = {
works: {}
};
}
componentWillMount() {
this.ref = base.syncState(`/works`
, {
context: this,
state: 'works'
})
}
componentWillUnmount() {
base.removeBinding(this.ref);
}
addWork(work) {
// update our state
const works = {...this.state.works};
// add in our new works with a timestamp in seconds since Jan 1st 1970
const timestamp = Date.now();
works[`work-${timestamp}`] = work;
// set state
this.setState({ works });
}
render() {
return(
<div>
<section className="portfolio">
<h3>Portfolio</h3>
<ul className="list-of-work">
{
Object
.keys(this.state.works)
.map(key => <Work key={key} details={this.state.works[key]}/>)
}
</ul>
</section>
</div>
)
}
}
export default Portfolio;
Inside of the Object i'm mapping through the Work component that is just a list item I have made another component for and isn't really relevant in the question.
Finally I have the Admin.js and AddWorkForm.js components. I abstracted the AddWorkForm.js so that I could use it elsewhere if need be, basically the main idea behind React Components, so that's why I chose to do it that way.
import React from 'react';
import Title from './Title';
import AddWorkForm from './AddWorkForm';
class Admin extends React.Component {
constructor() {
super();
this.addWork = this.addWork.bind(this);
// getInitialState
this.state = {
works: {}
};
}
addWork(work) {
// update our state
const works = {...this.state.works};
// add in our new works with a timestamp in seconds since Jan 1st 1970
const timestamp = Date.now();
works[`work-${timestamp}`] = work;
// set state
this.setState({ works });
}
render() {
return(
<div className="container-fluid">
<div className="container">
<div className="row">
<div className="col-sm-12">
<Title title="Admin"/>
<section className="admin">
<AddWorkForm addWork={this.addWork} />
</section>
</div>
</div>
</div>
</div>
)
}
}
export default Admin;
and the AddWorkForm.js component which is basically a form that onSubmit creates and object and resets the form
import React from 'react';
class AddWorkForm extends React.Component {
createWork(event) {
event.preventDefault();
console.log('Creating some work');
const work = {
name: this.name.value,
desc: this.desc.value,
image: this.image.value
}
this.props.addWork(work);
this.workForm.reset();
}
render() {
return(
<form ref={(input) => this.workForm = input} className="work-edit form-group" onSubmit={(e) => this.createWork(e)}>
<input ref={(input) => this.name = input} type="text" className="form-control" placeholder="Work Title"/>
<textarea ref={(input) => this.desc = input} type="text" className="form-control" placeholder="Work Description"></textarea>
<input ref={(input) => this.image = input} type="text" className="form-control" placeholder="Work Image"/>
<button type="submit" className="btn btn-primary">+Add Work</button>
</form>
)
}
}
export default AddWorkForm;
Here is the file that includes where I'm using ReactRouter:
import React from 'react';
import { render } from 'react-dom';
// To render one method from a package user curly brackets, you would have to know what method you wan though
import { BrowserRouter, Match, Miss} from 'react-router';
import './css/normalize.css';
import './css/bootstrap.css';
import './css/style.css';
// import '../js/bootstrap.js';
import App from './components/App';
import WorkItem from './components/WorkItem';
import Capability from './components/Capability';
import Connect from './components/Connect';
import NotFound from './components/NotFound';
import Admin from './components/Admin';
const Root = ()=> {
return(
<BrowserRouter>
<div>
<Match exactly pattern="/" component={App} />
<Match pattern="/work/:workId" component={WorkItem} />
<Match exactly pattern="/capability" component={Capability} />
<Match exactly pattern="/connect" component={Connect} />
<Match exactly pattern="/admin" component={Admin} />
<Miss component={NotFound} />
</div>
</BrowserRouter>
)
}
render (<Root />, document.querySelector('#main'));
So here's what I've tried and failed to accomplish, and it's likely some kind of this.props solution that I haven't been able to define, I need to create the work in Admin.js component, which creates the object and then have it throw that object to Portfolio.js component so it can render it via the Work.js component and it doesn't add the object to the DB.
This works when i put all the components on the same page, which isn't ideal because then anyone accessing my Portfolio could add a work. Sure I could start the process of learning authentication and how to make that component appear or disappear based on user credentials, but I'd much rather also learn the very valuable skill of being able to have my admin page on a separate view all together because I see another application for learning to do so.
Would love to hear others opinions on this and where they may be able to determine I'm failing here.
Btw, I realize I have other components like Nav.js and Title.js but they are not necessary in order to illustrate the example.
Thank you.
You can pass components as props and when using React Router you can have named components.
For data sharing between siblings is better advised to have the data on a parent component although you could use context, but this is not advised and may be unacessible on future versions.
If you need to create something on another component (don't know why) you could pass a function that would render it.

Get Input Value From Stateless Component

CONTEXT
I'm trying to get the value of an input field from a stateless component inside another stateless component and then use it to call a method. I'm using rebass for my UI component and doing this in Meteor + Mantra.
I understand that I could do this by using refs if I were using <input> HTML fields and not another stateless component.
PROBLEM
My current code yield preventDefault of undefined, and when removed, the console.log prints out each time the input changes, not on submit. I believe that my state applies to the entire Dashboard component, instead of the stateless Rebass <Input/>, but I do not know how to change this.
import React from 'react';
import {PageHeader, Container, Input,Button} from 'rebass';
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = { websiteUrl: ''};
this.onInputChange = this.onInputChange.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onInputChange(event) {
this.setState({websiteUrl:event.target.value});
}
onFormSubmit() {
event.preventDefault;
const {create} = this.props;
const {websiteUrl} = this.state.websiteUrl;
console.log(this.state.websiteUrl);
create(websiteUrl);
}
render() {
const { error } = this.props;
return (
<div>
<PageHeader
description="Dashboard Page"
heading="Dashboard"
/>
<Container>
<form>
<Input
value={this.state.websiteUrl}
type="text"
buttonLabel="Add Website"
label="Website"
name="add_website"
onChange={this.onInputChange}
/>
<Button
backgroundColor="primary"
color="white"
inverted={true}
rounded={true}
onClick={this.onFormSubmit()}
> Add Website </Button>
</form>
</Container>
</div>
);
}
}
export default Dashboard;
You should pass an event to the onFormSubmit function:
<Button
backgroundColor="primary"
color="white"
inverted={true}
rounded={true}
onClick={(event) => this.onFormSubmit(event)}
...

Categories