Parent State not updating when passed data from child component - javascript

I'm trying to create a note-taking application in React.
The application should add a new note when an "Add note" button is pressed with the value in the input box.
Unfortunately when I try to push the note to the list and update the parents state the changes aren't reflected on screen or in the react debugger.
The pushing of new note to list can be seen in the alert line but not anywhere else.
Here is the parent component containing the original notes state:
class NoteApplication extends React.Component {
constructor(props) {
super(props);
this.state = {
notes: Array(),
};
this.update = this.update.bind(this);
this.state.notes.push("Sample note");
}
update(notes) {
return () => {
this.setState({
notes: notes
});
}
}
render() {
return (
<div>
<h1>React Notes</h1>
<div class="InsertBarDiv">
<InsertBar
notes={this.state.notes}
update = {this.update}
/>
</div>
<div class="NotesDiv">
<Notes
notes={this.state.notes}
/>
</div>
</div>
)
}
}
And here is the child component
class InsertBar extends React.Component {
constructor(props) {
super(props);
this.state = {value:''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
const notes = this.props.notes.slice();
notes.push(this.state.value);
this.props.update(notes);
alert(notes);
event.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input class="noteInsertBar" type="text" name="" onChange={this.handleChange}/>
<input class="insertBut" type="submit" value="Add Note"/>
</form>
</div>
)
}
}
class Notes extends React.Component {
renderNote(i) {
return (
<div>
{this.props.notes}
</div>
)
}
render() {
return (
<div>
<h2>Notes:</h2>
<div class="FullNote">
{this.renderNote(1)}
</div>
</div>
)
}
}
I would expect the note to be pushed to the copy of the notes list & the parents state to be set to the new copy of the notes list.
I would then expect this to be displayed onscreen.

It's likely due to the fact that you're returning a function from update, you should just call setState when update gets called:
update(notes) {
setState({ notes });
}
Side note: You should avoid Array.push when dealing with arrays in React. The way you're doing it is fine because you're calling slice to copy the array before you push, but if you use concat or the spread operator, you'll be less likely to unintentionally introduce bugs.
const notes = this.props.notes.concat(this.state.value);
or:
const notes = [...this.props.notes, this.state.value];

I got some help in the react discord thanks to the user #WasaWasaWassup so I'd like to share what fixed my issue.
Mutating the parent state in the constructor to add a sample note was causing issues.
The second issue was my update function returning a function yet being called as if it wasn't.
Removing the constructor mutating & altering my update function to just set the state without an embedded function fixed all my issues and the notes array updates and displays correctly.

Related

Submitting form with textarea in React

I have a textarea that I want to stringify to JSON on form submission. I will even settle for just having the function set the textarea value.
import React from 'react';
export default class ClinicalMain extends React.Component {
constructor(props) {
super(props);
}
state = {selectedOption: ''}
// my function to update the textarea
reactStringify() {
let obj = {
name: "bob",
age: 4
}
console.log('in stringify');
let value = JSON.stringify(obj);
}
componentDidMount() { }
render() {
return (
<React.Fragment>
<form>
<button type="button"
onClick={this.reactStringify}
id="reactid"
>React stringify</button>
<textarea value={this.value}
defaultValue=""
rows="10" cols="80"
></textarea>
<br />
</form>
</React.Fragment>
)
}
}
let value does not update. Do I need to use setState? this?
There are a number of issues in the code indicating a lack of familiarity with the excellent React tutorial. As with any library, it's necessary to spend time reading the manual before diving in.
State should not be modified directly. Use this.setState() to replace state. this.setState() doesn't work instantly; it simply informs the React library that the state needs updating and React handles the update on its own when it sees fit.
Beyond this, let value = ... is a purely local variable, not a class variable, so this.value would be undefined in render no matter what; in other words, your code doesn't attempt to modify or access state in any way.
Class functions that attempt to access this need to be bound. For example, onClick={this.reactStringify} passes a reference to the this.reactStringify function, but this will be undefined inside of this.reactStringify unless an arrow function is used (which implicitly binds this), or this is explicitly bound:
this.handleChange = this.handleChange.bind(this);
Explicit is considered to be better practice for class components than the arrow function approach because it only requires one call to bind when the component is constructed.
React typically uses something called controlled components to listen to changes on a text field. This means that the element's value tracks component state and acts as the single source of truth.
While I'm not exactly sure what you're ultimately looking to accomplish, here's a working example to get you moving again which demonstrates the above concepts.
class ClinicalMain extends React.Component {
constructor(props) {
super(props);
this.state = {value: "", output: ""};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange({target: {value}}) {
this.setState(() => ({value}));
}
handleSubmit(e) {
e.preventDefault();
this.setState(state => ({
output: `you wrote: "${state.value}"`
}));
}
render() {
return (
<React.Fragment>
<form onSubmit={this.handleSubmit}>
<textarea
value={this.state.value}
onChange={this.handleChange}
></textarea>
<div>
<input type="submit" value="Show output" />
</div>
</form>
<div>{this.state.output}</div>
</React.Fragment>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<ClinicalMain name="World" />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
Here are relevant sections of the documentation which provide more detail:
State and Lifecycle
Handling Events
Forms

Clearing state and input values between parent and child components in React

Two part question here: First, can anyone explain to me why this.state.taskName and this.state.taskBody and their corresponding inputs aren't clearing after submitting the form? In handleSubmit() I'm using this.setState() to set their states to an empty string, but it doesn't seem to be working. It also wont let me submit more than once, which I suspect might have to do with the state not clearing.
Second, what would be the best way to push a task with multiple key-value pairs into the this.state.tasks array? I tried storing taskName and taskBody as an object in state, and also tried pushing the them into an object and then displaying them, but couldn't get it to work.
Here are parent, child, & sibling files:
import React, { Component } from 'react';
import Task from './Task/Task';
import NewTaskForm from './NewTaskForm/NewTaskForm';
class Board extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.state = {
tasks: [],
taskName: '',
taskBody: ''
};
}
handleSubmit(e) {
e.preventDefault();
let updatedTasks = this.state.tasks;
let taskName = this.state.taskName;
let taskBody = this.state.taskBody;
updatedTasks.push(taskName, taskBody);
let updatedName = '';
let updatedBody = '';
this.setState({ tasks: updatedTasks, taskName: updatedName, taskBody: updatedBody });
};
handleChange(e) {
this.setState({ [e.name]: e.value });
}
render() {
return (
<div>
<NewTaskForm
onSubmit={this.handleSubmit}
onChange={this.handleChange}
/>
<Task
tasks={this.state.tasks}
/>
</div>
);
}
}
export default Board;
import React, { Component } from 'react';
class NewTaskForm extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target);
}
render() {
return (
<form onSubmit={this.props.onSubmit}>
<label>Task Name</label>
<input
name="taskName"
required type="text"
value={this.props.taskName}
onChange={this.handleChange}
placeholder="Enter a task name"
/>
<label>Task Body</label>
<input
name="taskBody"
required type="text"
value={this.props.taskBody}
onChange={this.handleChange}
placeholder="Enter a task body"
/>
<button
type="submit"
className="btn btn-default"
>Add Task
</button>
</form>
);
}
}
export default NewTaskForm;
import React, { Component } from 'react';
class Task extends Component {
render() {
let taskList = this.props.tasks.map((task, i) => {
return (
<li key={i}>
{task}
</li>
);
});
return (
<ul>
{taskList}
</ul>
)
}
}
export default Task;
Thanks!
To address your first question, the reason the inputs aren't clearing is because you are not passing the taskName and taskBody values as props to <NewTaskForm />. The inputs aren't being controlled by React since NewTaskForm isn't receiving them, so they are currently entirely user-controlled. Add them and you'll see them clear after submitting the form.
The best way to hold a taskName/taskBody pair in state is as you said: an object. In your TaskComponent you'll need to change the mapping logic to work with an object, though, as well as make sure you push an object to this.state.tasks in Board. I've linked to a Fiddle that shows the changes I made: https://jsfiddle.net/0z89Lcpw/.
Specifically the changes I made versus your code are:
modified line 21 to push an object with shape {taskName, taskBody}
added lines 37 and 38 to pass taskName and taskBody props to NewTaskForm
changed line 95 (old: line 93) to pull taskName and taskBody off of each task and present both--of course you can present these pieces of data in quite a few different ways to suit your presentational purposes.
Please see your altered code below. Ive added explanations beneath for the main alterations I've made :)
class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
tasks: [],
taskName: '',
taskBody: ''
};
}
handleSubmit(e) {
e.preventDefault();
let tasks = this.state.tasks;
let taskName = this.state.taskName;
let taskBody = this.state.taskBody;
tasks.push({taskName, taskBody});
this.setState({tasks, taskName: '', taskBody: ''});
};
handleChange(e) {
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value});
}
render() {
return (
<div>
<NewTaskForm
taskName={this.state.taskName}
taskBody={this.state.taskBody}
onSubmit={(e) => this.handleSubmit(e)}
onChange={(e) => this.handleChange(e)}
/>
<Tasks
tasks={this.state.tasks}
/>
</div>
);
}
}
class NewTaskForm extends React.Component {
render() {
return (
<form onSubmit={this.props.onSubmit}>
<label>Task Name</label>
<input
name="taskName"
required type="text"
value={this.props.taskName}
onChange={(e) => this.props.onChange(e)}
placeholder="Enter a task name"
/>
<label>Task Body</label>
<input
name="taskBody"
required type="text"
value={this.props.taskBody}
onChange={(e) => this.props.onChange(e)}
placeholder="Enter a task body"
/>
<button type="submit" className="btn btn-default">Add Task</button>
</form>
);
}
}
class Tasks extends React.Component {
render() {
let taskList = this.props.tasks.map((task, i) => {
return (
<li key={i}>
<b>{task.taskName}</b><br/>
{task.taskBody}
</li>
);
});
return (
<ul>
{taskList}
</ul>
)
}
}
Passed through taskName and taskBody as props to your NewTaskForm
component to use in their inputs.
You were pushing the new task to your updated task list incorrectly.
In your Task component you were not showing the properties of the
task, you were attempting to display the task object.
Working fiddle: https://jsfiddle.net/8sLw4phf/2/
I can see couple of issues with the way you have written the code. For starters, you are not passing in taskName and taskBody as props to NewTaskForm, as the component expects the value to be read from the props.
Not a good idea to mutate the state
As name and the body encompasses into a task, maintain a shape for it.
Check this code snippet - https://codesandbox.io/s/ov675m6r7y
I would try something like this:
handleSubmit(e) {
e.preventDefault();
const { taskName, taskBody } = this.state;
this.setState({
tasks: [...this.state.tasks, { taskName, taskBody }]
taskName: '',
taskBody: ''
});
};
This way you are not mutating your state and your array contains one object per task.

React passing fetched data to another component

Hy!
I am having an issue with my react code. My task is to call from iTunes API which i do with fetch then I process the data. But I cannot save it as a variable to be able to pass it around later.
import React, { Component } from 'react';
class SearchField extends Component{
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = (event) => {
this.setState({value: event.target.value});
}
handleSubmit = (event) => {
event.preventDefault();
fetch(`https://itunes.apple.com/search?media=music&term=${this.state.value.toLowerCase()}`)
.then((resp) => resp.json())
.then(searchRes => searchRes.results[0].artistName)
.catch(err => console.log(err));
}
render() {
return(
<section className="hero is-primary">
<div className="hero-body">
<div className="container">
<form onSubmit={this.handleSubmit}>
<input className="input is-primary" type="text" value={this.state.value} onChange={this.handleChange} placeholder="Search for artist" />
<input className="button is-info" type="submit" value="Search" />
</form>
</div>
</div>
</section>
)
}
}
export default SearchField;
I'd have to use the fetched data later, i just need the artist name first.
If I log the value (searchRes.results[0].artistName, i get the correct value, but if i want to save it for later use i only got empty console.log back.
I've tried several approaches but I never get my result back.
Help me out please.
Remember that data flow in React is unidirectional. If you want to share the data around your app the search component should not be the component that fetches the data. That should be left to a parent component (maybe App). That component should have a function that handles the fetch request, and you can then pass a reference to that function down to the search component to call when the button is clicked. Then, once that data is loaded, the parent (App) component can pass all the relevant data down to the child components.
Here's a quick mock-up based on your existing code:
class Search extends {
constructor(props) {
super(props);
this.state = { url: '' };
this.handleKey = this.handleKey.bind(this);
}
handleKey(e) {
const url = e.target.value;
this.setState({ url });
}
render() {
const { url } = this.state;
// grab the function passed down from App
const { fetchData } = this.props;
return (
<input onKeyUp={this.handleKey} value={url} />
// Call that function with the url when the button is clicked
<button onClick={() => fetchData(url)}>Click</button>
)
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = { data: [] };
this.fetchData = this.fetchData.bind(this);
}
// App contains the fetch method
fetchData(url) {
fetch(url)
.then(res => res.json())
// Update the App state with the new data
.then(data => this.setState({ data });
}
render() {
const { data } = this.state;
// Sanity check - if the state is still empty of data, present
// a loading icon or something
if (!data.length) return <Spinner />
// otherwise return the rest of the app components
// passing in the fetch method as a prop for the search component
return (
<OtherComponent data={data} />
<Search fetchData={this.fetchData} />
)
}
}
Please specify what you mean by
but if i want to save it for later use i only got empty console.log back
I think the correct way to handle your problem is by passing a callback function to your component's props that gets called whenever you press search and a search result is found, like this: https://codesandbox.io/s/xpq171n1vz
Edit: Note that while this answer has been accepted and is a way to solve your problem, Andy's answer contains solid and elaborate advice on how to actually structure your components.

React access state [duplicate]

This question already has answers here:
React: "this" is undefined inside a component function
(11 answers)
Closed 4 years ago.
Hi Trying to learn React right now, still making just baby steps.
I wrote code bellow in codepen(see link on the bottom), in my code I put a few log to console statements I can't figure out why my function handleSubmit which is inside upmost component('TodoApp') cannot access state?
I figured it cannot access it because I can print to console text just above 'let current_todos = this.state.todos' but I never see in console text just bellow it.
If this is incorrect how am I supposed to access state then?
NOTE: I realize that a lot of code in that function is redundant but I declare these variables and log statements for debugging purposes
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
todos : [ ]
}
}
render() {
return (
<div className='todo-comp todo-app'>
<h2>ToDo App</h2>
<form onSubmit={this.handleSubmit}>
<input type="text">
</input>
</form>
<TodoList todos={this.state.todos}/>
</div>
)
}
handleSubmit(event) {
let new_todo = event.target.children[0].value
console.log("Submited: ".concat(new_todo))
let current_todos = this.state.todos
console.log("Succesfully accessed state")
this.setState({"todos" : this.state.todos.push(new_todo)})
}
}
class TodoList extends React.Component {
constructor(props) {
super(props)
}
render () {
return (
<ul className="todo-comp todo-list">
{this.props.todos.map(
function(item,key) {
return(
<li key={key} className="todo-comp todo-item">{item}</li>
)
})}
</ul>
)
}
}
ReactDOM.render(
<TodoApp />,
document.getElementById('app'),
console.log("App has been rendered"))
My CodePen Link
The first mistake is that your handleSubmit will be recreated on every render.
This code will allow you to see the input value and submit etc. Hope this helps, if you have anymore questions just comment underneath.
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.handleSubmit = this.handleSubmit.bind(this)
this.onChange= this.onChange.bind(this)
this.state = {
todos : [ ]
}
}
onChange(event) {
this.setState({ text: e.target.value })
}
handleSubmit(event) {
const { text } = this.state;
// Your submit value;
console.log(text)
}
render() {
return (
<div className='todo-comp todo-app'>
<h2>ToDo App</h2>
<form onSubmit={this.handleSubmit}>
<input type="text" onChange={this.onChange}>
</input>
</form>
<TodoList todos={this.state.todos}/>
</div>
)
}
}
When calling this.handleSubmit, you should add .bind(this), since the context is different on invocation time. Another option would be to add the following line in the constructor:
this.handleSubmit = this.handleSubmit.bind(this)

Update component from select value ReactJS

There is a select which gets ID's from an API,
then i have a table that displays data.
if i define a state like the example this.state = {value:23};
the table displays the data without problems from http://.../23
What i'm trying to achieve is that the table gets updated after change the select.
i can get the selected value by console.log( event.target.value); but i'm getting stuck trying to pass this value to:
< Table value={this.state.value} />
and re-rendering the table! any help is appreciated!
class Planet extends React.Component {
constructor(props) {
super(props);
this.state = {value: 23};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({value: event.target.value});
console.log(event.target.value);
}
handleSubmit(event){
this.setState({value: this.state.value});
//event.preventDefault();
}
render () {
let planets = this.props.state.planets;
let optionItems = planets.map((planet) =>
<option key={planet.id}>{planet.id}</option>
);
return (
<div>
<form onSubmit={this.handleSubmit}>
<select value={this.state.value} onChange={this.handleChange} className="dropbox" >
{optionItems}
</select>
<input type="submit" value="Submit" />
</form >
<Table value={this.state.value} />
</div>
)
}
export default class Table extends React.Component {
constructor(props){
super(props);
this.state = {}
}
fetchData() {
const url = 'http://localhost:8000/sprints/';
const value = this.props.value;
var string = url+value;
fetch(string)
.then(function(response) {
return response.json();
})
.then((myJson) => this.setState(myJson));
}
componentDidMount(){
this.fetchData();
}
render() {
return this.state.sprints ? (
<div>
<ResponseTable data={this.state.sprints} />
</div>
) : (
<div>
Loading ...
</div>
);
}
}
Actually, you don't have any problems with passing props. You should make additional request using componentDidUpdate lifecycle hook after your component is updated, but don't forget to check if value is changed.
Like this:
Table component
componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value) {
this.fetchData()
}
}
You have to use either componentWillReceiveProps(props) inside Table class which will get called everytime you update state in parent component. Inside that method you can recall fetch method again
https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops

Categories

Resources