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)
Related
I have a React.Component with render() declared this way:
render(){
return <div>
<button id="butt" onClick={()=> $("#noti").change("test") }>click me</button>
<Notification id="noti" onMounted={() => console.log("test")}/>
</div>
}
And this is my Notification class:
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
message: "place holder",
visible: false
}
}
show(message, duration){
console.log("show")
this.setState({visible: true, message})
setTimeout(() => {
this.setState({visible: false})
}, duration)
}
change(message){
this.setState({message})
}
render() {
const {visible, message} = this.state
return <div>
{visible ? message : ""}
</div>
}
}
As the class name suggests, I am trying to create a simple notification with message. And I want to simply display the notification by calling noti.show(message, duration).
However, when I try to find noti by doing window.noti, $("#noti") and document.findElementById("noti"), they all give me undefined, while noti is displayed properly. And I can find the butt using the code to find noti.
How should I find the noti? I am new to front end so please be a little bit more specific on explaining.
It's not a good idea using JQuery library with Reactjs. instead you can find a appropriate react library for notification or anything else.
Also In React we use ref to to access DOM nodes.
Something like this:
constructor(props) {
super(props);
this.noti = React.createRef();
}
...
<Notification ref={this.noti} onMounted={() => console.log("test")}/>
more info: https://reactjs.org/docs/refs-and-the-dom.html
I have hardcoded the id to 'noti' in the render method. You can also use the prop id in the Notification component.I have remodelled the component so that you can achieve the intended functionality through React way.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messageContent: 'placeholder'
}
}
setMessage = (data) => {
this.setState({messageContent : data});
}
render() {
return (
<div className="App">
<button id='butt' onClick= {() => this.setMessage('test')} />
<Notification message = {this.state.messageContent} />
</div>
);
}
}
class Notification extends React.Component {
render () {
const {message} = this.props;
return (
<div id='noti'>
{message}
</div>
)
}
}
Before beginning: Using id/class to reach DOM nodes is not suggested in React.js, you need to use Ref's. Read more at here.
In your first render method, you give id property to Notification component.
In react.js,
if you pass a property to some component, it becomes a props of that
component. (read more here)
After you give the id to Notification, you need to take and use that specific props in your Notification component.
You see that you inserted a code line super(props) in constructor of Notification? That means, take all the props from super (upper) class and inherit them in this class.
Since id is HTML tag, you can use it like:
class Notification extends React.Component {
constructor(props) {
// inherit all props from upper class
super(props);
this.state = {
message: "place holder",
visible: false,
// you can reach all props with using this.props
// we took id props and assign it to some property in component state
id: this.props.id
}
}
show(message, duration){
// code..
}
change(message){
// code..
}
render() {
const {visible, message, id} = this.state
// give that id to div tag
return <div id={id}>
{message}
</div>
}
}
You can't pass id/class to a React Component as you would declare them in your normal HTML. any property when passed to a React Component becomes a props of that component which you have to use in the component class/function.
render() {
const {visible, message} = this.state
// give your id props to div tag as id attr
return <div id={this.props.id}>
{message}
</div>
}
This answer does not provide the exact answer about selecting a component as you want. I'm providing this answer so you can see other alternatives (more React way maybe) and improve it according to your needs.
class App extends React.Component {
state = {
isNotiVisible: false
};
handleClick = () => this.setState({ isNotiVisible: true });
render() {
return (
<div>
<button onClick={this.handleClick}>Show Noti</button>
{this.state.isNotiVisible && (
<Noti duration={2000} message="This is a simple notification." />
)}
</div>
);
}
}
class Noti extends React.Component {
state = {
visible: true
};
componentDidMount() {
setTimeout(() => this.setState({ visible: false }), this.props.duration);
}
render() {
return this.state.visible && <div>{this.props.message}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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="root" />
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.
So I know this question has been asked a couple of times and the general concession is that props cant be changed when it has already passed down to a child. The situation I have here is that basically i have a different onClick function in a different file that updates the the id="movie-header" with an innerHTML, the DOMSubtreeModified and componentDidUpdatedetects the change and pass down the new props to Child "Ebay".
So the question here is how do I get the Ebay component to update its state and make use of the new value with every change to the state in the moviemodalwindow(the parent of the Ebay)
MovieModalWindow.js
import React from "react";
import "../MovieGo.css";
import Ebay from "../Store/Ebay";
class MovieModalWindow extends React.Component {
constructor() {
super();
this.state = {
name: 1
};
}
componentDidMount() {
var element = document.getElementById("movie-header");
element.addEventListener("DOMSubtreeModified", this.myFunction(element));
var name = this.state.name + 1;
this.setState({ name: [...this.state.name, name] });
}
myFunction = input => event => {
this.setState({ name: input.innerHTML });
};
componentDidUpdate(prevProps, prevState) {
if (prevState.name != this.state.name) {
window.localStorage.setItem("keyword", this.state.name);
}
}
render() {
return (
<div id="myModal" class="modal">
<div class="modal-content">
<span onClick={onClose} class="close">
×
</span>
<h1 id="movie-header" />
<div className="middle-window">
<div className="left">
<Ebay id="ebay" keyword={this.state.name} />
</div>
</div>
<h3>PLOT</h3>
<p id="moviedetails" />
</div>
</div>
);
}
}
export default MovieModalWindow;
Ebay.js File
import React from "react"
class Ebay extends React.Component{
constructor(){
super();
this.state={
data:[],
}
}
componentWillUpdate(prevProps, prevState){
if (prevProps.keywords!=this.props.keywords){
console.log(window.localStorage.getItem("keyword"))
}
render(){
const{newInput} =this.props
return(
<div>
</div>
)
}
}
export default Ebay
I'm unsure if I'm answering the question you're asking, so apologies if this isn't what you're asking.
Step 1. Make Ebay's prop's change when you need this update to happen. (I think you stated you already have this occurring?)
Step 2: Make Ebay's state update when the props change. Here you can just watch for prop changes with componentWillReceiveProps and update the state accordingly.
class Ebay extends React.Component {
constructor() {
super();
this.state = { data: [] };
}
componentWillRecieveProps(nextProps) {
if (nextProps.keyword !== this.props.keyword) {
this.setState({ data: ['something new'] });
}
}
render() { ... }
}
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
I am building a movie search React app using themoviedb.org API. in order to an make ajax call to pull the list of movies I need to get input value as a variable and feed to the url, but not sure how to fetch a value that belongs to another component.
I've made an extensive online search, but they mainly refer to the case when it happens inside the same component, and using ref is discouraged.
So what would be the best way (or at least most common or the simplest way) to fetch the input value variable from one component to pass down to another and attach to the end of url, while:
1) Keeping global space clean
2) Organizing the entire app in 'React way'
3) Keeping components decoupled
?
Would React Router necessary in this case?
import React from 'react';
import './App.css';
import axios from 'axios';
class SearchForm 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) {
console.log("state value is " + this.state.value);
var searchValue = this.movieName.value;
console.log("ref value is "+ searchValue)
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input className="movieName" type="text" ref={(input) => { this.movieName = input; }} value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
<h1>{this.state.value}</h1>
</form>
);
}
}
class App extends NameForm{ /* I am extending NameForm to get access to input value, but probably coupling components too tight */
constructor(props) {
super(props);
this.state ={
movie:[]
};
}
componentDidMount() {
let searchInput = "land"; /* This should be from SearchForm's input value */
let sortByPop = "&sort_by=popularity.desc";
let requestUrl = 'https://api.themoviedb.org/3/search/movie?api_key=f8c4016803faf5e7f424abe98a04b8d9&query=' + searchInput + sortByPop;
axios.get(requestUrl).then(response => {
this.setState({movie: response.data.results})
});
}
render() {
let baseImgURL = "https://image.tmdb.org/t/p/w185_and_h278_bestv2";
let posterImgPath = this.state.movie.map(movie => movie.poster_path);
let posterLink = baseImgURL + posterImgPath;
return(
<div className="App">
<Header />
<SearchForm />
<div>
{this.state.movie.map(movie =>
<div className="movieTitle">
<div className="movieCard">
<img className="posterImg" src= {`https://image.tmdb.org/t/p/w185_and_h278_bestv2/${movie.poster_path}`} alt={movie.title} />
<div className="searchFilmTitles" key={movie.id}>{movie.title}</div>
</div>
</div>
)}
</div>
</div>
)
}
}
export default App;
componentDidMount get called only once when your component get attached to the page. So it's not the correct place to call you search API. Instead, you should call it every time when the user clicks 'submit' button. For that, you need to bubble handleSubmit trigger to App component by passing a callback method as a prop to SearchForm component. Also, you don't need to use ref as you already have search text in your state.
SearchForm
class SearchForm 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) {
event.preventDefault();
if(this.props.onSubmit && typeof this.props.onSubmit === "function"){
this.props.onSubmit(this.state.value);
}
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input className="movieName" type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
<h1>{this.state.value}</h1>
</form>
);
}
}
App
class App extends React.Component { /* I'm not sure why you extends NameForm and what NameForm does */
constructor(props) {
super(props);
this.state = {
movie:[]
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(value) {
let searchInput = value // you get the value of movieName input here
let sortByPop = "&sort_by=popularity.desc";
let requestUrl = 'https://api.themoviedb.org/3/search/movie?api_key=f8c4016803faf5e7f424abe98a04b8d9&query=' + searchInput + sortByPop;
axios.get(requestUrl).then(response => {
this.setState({movie: response.data.results})
});
}
render() {
let baseImgURL = "https://image.tmdb.org/t/p/w185_and_h278_bestv2";
let posterImgPath = this.state.movie.map(movie => movie.poster_path);
let posterLink = baseImgURL + posterImgPath;
// I'm not sure why use need above code as you don't use it anywhere
return(
<div className="App">
<Header />
<SearchForm onSubmit={this.handleSubmit}/>
<div>
{this.state.movie.map(movie =>
<div className="movieTitle">
<div className="movieCard">
<img className="posterImg" src= {`https://image.tmdb.org/t/p/w185_and_h278_bestv2/${movie.poster_path}`} alt={movie.title} />
<div className="searchFilmTitles" key={movie.id}>{movie.title}</div>
</div>
</div>
)}
</div>
</div>
);
}
}