Hi I was trying to setState for my app based on the values of Props I was relying on values of prop for my state , But when I passed the function to setState the props I received were undefined , don't know why , I followed this document for reference. You can read the section that says State Updates May Be Asynchronous
Below is my code (code snippet from File Form.js)
this.props.setTodos(function (_, props) {
console.log("this is props:" + props);
return [
...props.todos,
{ text: props.inputText, completed: false, id: Math.random() * 1000 },
];
});
Code for App.js File where I pass props
import "./App.css";
import Form from "./components/Form";
import TodoList from "./components/TodoList";
import React, { useState } from "react";
function App() {
const [inputText, setText] = useState("");
const [todos, setTodos] = useState([]);
return (
<div className="App">
<header>
<h1>My Todolist </h1>
</header>
<Form
todos={todos}
setTodos={setTodos}
setText={setText}
inputText={inputText}
/>
<TodoList />
</div>
);
}
export default App;
Full code for Form.js file
import React from "react";
class Form extends React.Component {
constructor(props) {
super(props);
}
handler = (e) => {
// console.log(e.target.value);
this.props.setText(e.target.value);
};
submitTodoHandler = (e) => {
e.preventDefault();
this.props.setTodos(function (_, props) {
console.log("this is props:" + props);
return [
...props.todos,
{ text: props.inputText, completed: false, id: Math.random() * 1000 },
];
});
};
render() {
return (
<div>
<form>
<input onChange={this.handler} type="text" className="todo-input" />
<button
onClick={this.submitTodoHandler}
className="todo-button"
type="submit"
>
<i className="fas fa-plus-square"></i>
</button>
<div className="select">
<select name="todos" className="filter-todo">
<option value="all">All</option>
<option value="completed">Completed</option>
<option value="uncompleted">Uncompleted</option>
</select>
</div>
</form>
</div>
);
}
}
export default Form;
I don't know why my props are undefined ? Thanks
What you need to do is to change submitTodoHandler function in your <Form> component:
submitTodoHandler = (e) => {
e.preventDefault();
console.log(this.props.inputText) // outputs what you typed in the input
this.props.setTodos(this.props.inputText);
};
You define setState in functional component and pass it as a prop to class component. setState hook in functional components behaves slightly different from setState in class components, which you are referencing to.
In functional components setState (or setTodos in your case) changes the state of the variable simply by using setState(newVariableValue) and accepts as a parameter previous state. As newVariableValue is passed with a prop (inputText) from <App> component to <Form> component, you can directly access it with this.props.inputText.
Using the State Hook
Related
I am having a question about how to implement a callback function. In my case, I have a React app with this structure: App > Child > Button components
The problem is I do not know how to write a callback function from Button to Child
I would like to update a value in Child (e.g: inputFromButton) after clicking the button in Button Component. The handleClick() is triggered and a value will be sent to the Child component.
Could someone help me to do this?
Here is my code:https://codesandbox.io/s/nifty-stonebraker-0950w8
The App component
import React from 'react';
import Child from './Child';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: 'Data from App'
}
}
handleCallback = (childData) => {
this.setState({ data: childData })
}
render() {
const { data } = this.state;
return (
<div>
<Child dataFromApp={data} />
</div>
)
}
}
export default App
The Child component
import React from 'react';
import { renderButton } from './Button';
class Child extends React.Component {
state = {
inputFromApp: "",
inputFromButton: ""
}
componentDidMount() {
this.setState({
inputFromApp: this.props.dataFromApp
})
}
render() {
const renderButtonItem = renderButton(this.props);
const inputFromApp = this.state.inputFromApp
const inputFromButton= this.state.inputFromButton
return (
<div>
<input value={inputFromApp}></input>
<br></br>
<input value={inputFromButton}></input>
<div>{renderButtonItem}</div>
</div>
)
}
}
export default Child
The Button component
import React from 'react';
export const renderButton = (props) => {
const handleClick = () => {
console.log('handleClick() props data from App: ' + props.dataFromApp)
}
return (
<button onClick={handleClick}>Click</button>
)
}
renderButton is a function component and, therefore, needs to be in PascalCase: RenderButton (although it would be better off as Button).
Move handleClick to the Child component.
Then in Button the call to handleClick should be props.handleClick since handleClick will now be a property of the props object passed into the component. We don't need to pass down the data as a prop to the button but can, instead just log the data prop passed into Child.
handleClick = () => {
console.log(`handleClick(): ${props.dataFromApp}`);
}
In Child, instead of calling renderButton, import Button, and then use that in the render passing down the handler in the props. By doing this you're making the component as "dumb" as possible so it can be reused elsewhere in the application.
<Button handleClick={this.handleClick} />
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'm all new to react and am currently trying to modify an useState hook from another File. When one of the radio buttons from "Options.tsx" get's selected, the result should somehow be updated with the setResult function of useState hook so the Tag gets updated.
I think I almost got it, but I don't manage to pass the correct 'onSelect' Property to Options.tsx so it is updated.
Here's my code so far:
App.tsx
import React from 'react';
import './App.css';
import { useState } from 'react';
import { Result, ResultType } from './Result'
import { Options } from './Options'
function App() {
const [result, setResult] = useState<ResultType>('pending')
return (
<div className="App">
<header className="App-header">
<Options onSelect={props.onSelect} />
<Result result={result} />
</header>
</div>
);
}
export default App;
Options.tsx
import React from 'react'
interface Props {
onSelect: (correct: boolean) => void
}
export const Options = ({onSelect}: Props) => {
// TODO
const setWrong = () => setResult('wrong');
const setCorrect = () => setResult('correct');
return(
<div>
<fieldset>
<input type='radio' id='option1' onSelect={setWrong}/>
<label htmlFor='option1'>Label 1</label>
<input type='radio' id='option2' onSelect={setCorrect}/>
<label htmlFor='option2'>Label 2</label>
<input type='radio' id='option3' onSelect={setCorrect}/>
<label htmlFor='option3'>Label 3</label>
</fieldset>
</div>
)
}
Result.tsx (just for completion - works fine so far)
import React from 'react'
export type ResultType = 'pending' | 'correct' | 'wrong'
interface Props {
result: ResultType
}
export const Result = ({ result }: Props) => {
switch (result) {
case 'pending':
return <h2>Make a guess</h2>
case 'correct':
return <h2>Yay, good guess!</h2>
case 'wrong':
return <h2>Nope, wrong choice...</h2>
}
}
Any idea, how I can update the useState from Options.tsx?
Thank you in advance!
It's quite simple - you just need to propagate the setter via properties, to Options.
<Options setResult={setResult} />
Or, provide your own method which uses setResult, depending on circumstances.
I would note though that the value you're currently passing down to the onSelect, appears to be bound to an incorrect value. Typescript compiler is probably complaining about it?
You can pass the updater function to Options component:
<Options setResult={setResult} />
then in your Options Component you can use
props.setResult('blah')
Just pass setResult prop to Options component.
App.tsx:
function App() {
const [result, setResult] = useState<ResultType>('pending')
return (
<div className="App">
<header className="App-header">
<Options onSelect={props.onSelect} setResult={setResult} />
<Result result={result} />
</header>
</div>
);
}
Options.tsx:
export const Options = ({onSelect, setResult}: Props) => {
const setWrong = () => setResult('wrong');
const setCorrect = () => setResult('correct');
...
}
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.
When editing a record, the data being displayed on the component belongs to the props. I get an error
Warning: Failed form propType: You provided a value prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultValue. Otherwise, set either onChange or readOnly. Check the render method of TerritoryDetail.
I have a feeling I implemented my edit record component the wrong way based on what the docs say involving controlled components.
When editing a record, should you not use props for the field values? If that is the case, I have values of the record in my application state, but how do I sync my application state to my component state without using props?
In addition, the props say what value the select option should be on edit. But component state is used to monitor changes in the select option. How would component state update the props of the record, when the props are being set by application state and not component state?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getTerritory, getTerritoryMetaData, updateTerritory, modal } from '../actions/index';
import { Link } from 'react-router';
import { reduxForm } from 'redux-form';
import TerritoryTabs from './territory-tabs';
class TerritoryDetail extends Component {
constructor(props) {
super(props);
this.openSearchUserQueueModal = this.openSearchUserQueueModal.bind(this);
this.setAssignedToType = this.setAssignedToType.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
componentWillMount() {
// console.log(this.props);
this.props.getTerritory(this.props.params.id);
this.props.getTerritoryMetaData();
}
renderTerritoryPickList(fieldName) {
return this.props.territoryFields.map((territoryField) => {
const shouldRender = territoryField.name === fieldName;
if (shouldRender) {
return territoryField.picklistValues.map((option) => {
return<option value={option.value}>{option.label}</option>;
});
}
});
}
setAssignedToType(event) {
this.setState({ assignedToType : event.target.value });
}
openSearchUserQueueModal(searchType) {
this.props.modal({
type: 'SHOW_MODAL',
modalType: 'USER_QUEUE_SEARCH',
modalProps: {searchType}
})
}
onSubmit() {
console.log('Update button being clicked');
this.props.updateTerritory({
Name: this.refs[ `Name`].value,
tpslead__Type__c: this.refs[ `tpslead__Type__c`].value,
tpslead__Assigned_To_Type__c: this.refs[ `tpslead__Assigned_To_Type__c`].value,
tpslead__Assigned_To__c: this.refs['tpslead__Assigned_To__c'].value,
tpslead__Assigned_To_ID__c: this.refs['tpslead__Assigned_To_ID__c'].value
}, this.props.params.id);
}
onChangeTerritoryName(event) {
this.props.
}
render() {
if(!this.props.territory) {
return <div>Loading...</div>;
}
return(
<TerritoryTabs id={this.props.params.id} listTab="detail">
<div className="slds-form">
<div className="slds-form-element">
<div className="slds-form-element__label">
<label className="slds-align-middle" htmlFor="input1">Lead Territory Name</label>
</div>
<div className="slds-form-element__control">
<input type="text" ref="Name" className="slds-input" value={this.props.territory.Name}/>
</div>
</div>
<div className="slds-form-element">
<label className="slds-form-element__label" htmlFor="input2">Type</label>
<div className="slds-form-element__control">
<div className="slds-select_container">
<select ref="tpslead__Type__c" className="slds-select" value={this.props.territory.tpslead__Type__c}>
<option></option>
{this.renderTerritoryPickList('tpslead__Type__c')}
</select>
</div>
</div>
</div>
<div className="slds-form-element">
<label className="slds-form-element__label" htmlFor="input3">Assigned to Type</label>
<div className="slds-form-element__control">
<div className="slds-select_container">
<select ref="tpslead__Assigned_To_Type__c" onChange={ this.setAssignedToType } className="slds-select" value={this.props.territory.tpslead__Assigned_To_Type__c}>
<option></option>
{this.renderTerritoryPickList('tpslead__Assigned_To_Type__c')}
</select>
</div>
</div>
</div>
<div className="slds-form-element">
<label className="slds-form-element__label">Assigned To</label>
<div className="slds-form-element__control">
<section className="slds-clearfix">
<input ref="tpslead__Assigned_To__c" value={this.props.territory.tpslead__Assigned_To__c} className="slds-input slds-float--left" style={{maxWidth: '95%'}} disabled/>
<input ref="tpslead__Assigned_To_ID__c" value={this.props.territory.tpslead__Assigned_To_ID__c} type="hidden" />
<button onClick={this.openSearchUserQueueModal.bind(this, this.props.territory.tpslead__Assigned_To_Type__c)} className="slds-button slds-button--icon-border slds-float--right" aria-live="assertive" style={{display: 'inline'}}>
<svg className="slds-button__icon" aria-hidden="true">
<use xlinkHref={searchIcon}></use>
</svg>
</button>
</section>
</div>
</div>
<div className="slds-form-element slds-p-top--small">
<Link to="/" className="slds-button slds-button--neutral">
Cancel
</Link>
<button type="button" onClick={this.onSubmit} className="slds-button slds-button--brand">Update</button>
</div>
</div>
</TerritoryTabs>
);
}
}
function mapStateToProps(state) {
console.log(state);
return { territory: state.territories.single,
territoryFields: state.territories.fields
};
}
export default connect(mapStateToProps, { getTerritoryMetaData, getTerritory, updateTerritory, modal })(TerritoryDetail);
A controlled component means that you've provided both a value and an onChange handler. You have to have both, or React will complain. This is also true if you pass a null or undefined value, so you'll want to default to an empty string in those cases. Example:
export function TerritorySelect({ territory = '', options, onChange }) {
const choices = options.map((o, i) => (
<option key={i} value={o.value}>{o.label}</option>
));
const update = e => onChange(e.target.value);
return (
<select value={territory} onChange={update}>
{choices}
</select>
);
}
export default connect(
state => ({ territory: state.territory.get('territory') }),
{ onChange: actions.updateTerritory }
)(TerritorySelect)
Golden rule is that if the data will be changed by user's input then use the state, otherwise use props. To avoid confusion between application state and component state, I will call application state Redux store.
You can pass whatever is in your Redux store, with the function you already have mapStateToProps and on the constructor of your component simply setting them to the state. But the props are always needed, just not in the way you are using it. To do that on the constructor method, just add the following:
this.state = {
// whatever you want to define goes here
// if you want to pass props you don't need to call this, just props.foo
}
That means that you can handle the state of your select and the state of your inputs without an issue.
Your props will be updated whenever they receive new props as long as they are not received by user interaction. That means that you can use your Redux store to dispatch actions, what will fire an update to your props.