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)}
...
Related
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
I am facing issues while updating the values. Initially I am taking the values from the parent class to put into the text box, and then if I want to update the values into the form through the child component it should basically set the state in child component and pass the updated values to the API. But now when I try to change the values in the text box, it only changes one character and doesn't keep track of the state of all the props. How can I solve this? I have tried using the defaultValue it does change the values but it cannot keep track of the state change.
PS: The updateToApi is just a sample function that is using post to update values into the api
my sample project is here
https://codesandbox.io/s/sad-perlman-ukb68?file=/src/parent.js
#class Parent#
import React from "react";
import "./styles.css";
import Child from "./child";
class Parent extends React.Component {
constructor() {
super();
this.state = {
data: {
username: ["mar"],
name: [null]
}
};
}
updateToApi(data) {
var username: data.username;
var name: data.name;
}
render() {
return (
<Child data={this.state.data} updateToApi={this.updateToApi.bind(this)} />
);
}
}
export default Parent;
##class Child##
import React from "react";
import "./styles.css";
import { Button } from "react-bootstrap";
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "",
name: ""
};
}
handleSubmit = e => {
e.preventDefault();
};
handleChange = e => {
const data = { ...this.state };
data[e.currentTarget.name] = e.currentTarget.value;
this.setState({ data });
};
render() {
return (
<>
<form onSubmit={this.handleSubmit}>
<label>
Username:
<input
type="text"
name="username"
value={
this.props.data.username !== "undefined"
? this.props.data.username
: this.state.username
}
onChange={this.handleChange}
/>
</label>
<b />
<label>
Name:
<input
type="text"
name="Name"
value={
this.props.data.name !== "undefined"
? this.props.data.name
: this.state.name
}
onChange={this.handleChange}
/>
</label>
<br />
<Button variant="primary" onClick={this.props.updateToApi} />
</form>
</>
);
}
}
export default Child;
Why do you have 2 separate states? You should get rid of the state in your Child component entirely and only work with the Parent's state. Put HandleChange function in your Parent component also and pass it down through props.
UPD
Well, if you want for changes in your inputs to be visible, you could change the onchange handler in your Child coponent to
handleChange = e => {
this.setState({
[e.currentTarget.name] : e.currentTarget.value });
};
and the Input value just to this.state.username
Though i'm still having hard time to grasp what you are trying to accomplish here. Having 2 separate conditional states for the input fields is just too complicated. Imagine if your app would be a bit more complex? You'd lost yourself to debugging this stuff:
value={ this.props.data.username !== "undefined"
? this.state.username
: this.state.username
}
So here i highly recommend you to reevaluate all your data strucuture and data flow within the app. You should have the least amount of sources of truth within your app. Ideally one. So just use the main state in the Parent component and pass down the props that are required.
i want to show my functional component in class base component but it is not working. i made simpletable component which is function based and it is showing only table with some values but i want to show it when i clicked on Show user button.
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.userList = this.userList.bind(this);
}
userList = () => {
//console.log('You just clicked a recipe name.');
<SimpleTable/>
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList} >
Show User List
</Button>
</div>
);
}
}
export default ShowUser;
Why your code is not working
SimpleTable has to be rendered, so you need to place it inside the render method. Anything that needs to be rendered inside your component has to be placed there
On Click can just contain SimpleTable, it should be used to change the value of the state variable that controls if or not your component will be shown. How do you expect this to work, you are not rendering the table.
Below is how your code should look like to accomplish what you want :
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.state = { showUserList : false }
this.userList = this.userList.bind(this);
}
showUserList = () => {
this.setState({ showUserList : true });
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.showUserList} >
Show User List
</Button>
{this.state.showUserList ? <SimpleTable/> : null}
</div>
);
}
}
export default ShowUser;
You can also add a hideUserList method for some other click.
Or even better a toggleUserList
this.setState({ showUserList : !this.state.showUserList});
If you're referring to the method userList then it appears that you're assuming there is an implicit return value. Because you're using curly braces you need to explicitly return from the function meaning:
const explicitReturn = () => { 134 };
explicitReturn(); <-- returns undefined
const implicitReturn = () => (134);
implicitReturn(); <-- returns 134
The problem lies with how you are trying to display the SimpleTable component. You are using it inside the userList function, but this is incorrect. Only use React elements inside the render method.
What you can do instead is use a state, to toggle the display of the component. Like this:
const SimpleTable = () => (
<p>SimpleTable</p>
);
class ShowUser extends React.Component {
constructor(props) {
super(props);
this.state = {showSimpleTable: false};
this.toggle= this.toggle.bind(this);
}
toggle = () => {
this.setState(prev => ({showSimpleTable: !prev.showSimpleTable}));
}
render() {
return (
<div>
<button variant = "contained" color = "primary" onClick={this.toggle}>
Show User List
</button>
{this.state.showSimpleTable && <SimpleTable />}
</div>
);
}
}
ReactDOM.render(<ShowUser />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
The functionality you are looking for is called Conditional Rendering. The onClick prop function is an event handler and events in react may be used to change the state of a component. That state then may be used to render the components. In normal vanilla javascript or jQuery we call a function and modify the actual DOM to manipulate the UI. But React works with a virtual DOM. You can achieve the functionality you are looking for as follows:
class ShowUser extends Component {
constructor(props) {
super(props)
// This state will control whether the simple table renders or not
this.state = {
showTable: false
}
this.userList.bind(this)
}
// Now when this function is called it will set the state showTable to true
// Setting the state in react re-renders the component (calls the render method again)
userList() {
this.setState({ showTable: true })
}
render() {
const { showTable } = this.state
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList}>
Show User List
</Button>
{/* if showTable is true then <SimpleTable /> is rendered if falls nothing is rendered */}
{showTable && <SimpleTable />}
</div>
)
}
}
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.
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.