How do i update my state before it renders? - javascript

constructor(props) {
super(props);
this.state = {
movie: "Interstellar",
movies: [],
newMovieParsed: {
movieTitle: '',
moviePosters: '',
moviePlot: '',
movieGenre: '',
movieBoxOffice: '',
movieRatings: [],
movieActors: '',
imdbId: '',
}
};
}
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
this.componentWillMount();
};
componentWillMount() {
this.APIURL = `http://www.omdbapi.com/? s=${this.state.movie}&apikey=${API_KEY}`;
console.log(this.APIURL);
fetch(this.APIURL)
.then(resp => resp.json())
.then(data => {
const movies = data.Search;
this.setState(state => ({
...state, movies
}));
});
}
Now in the render
<Search
placeholder="Enter the title of a movie you wish to search and press Enter .."
onSearch={(value) => this.onSubmit(value)}
style={{ width: '100%' }}
/>
Everything works but when I put a new movie and press enter I have to enter twice. The first enter seems to be updating the state then the second updates the render. How do i update the state and render it with the first enter? I am also using Ant-design.

The main thing is your setState callback should either be the name of the function (no parenthesis) or an anonymous function that calls it:
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
this.componentWillMount;
};
OR
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
() => this.componentWillMount();
};
Other hints:
Usually you don't call lifecycle methods directly (componentWillMount), and you don't need to do that much work in setState. You can set just the key you want to replace.
Here is some optimized code if you are interested:
constructor(props) {
super(props);
this.state = {
movie: "Interstellar",
movies: [],
newMovieParsed: {
movieTitle: '',
moviePosters: '',
moviePlot: '',
movieGenre: '',
movieBoxOffice: '',
movieRatings: [],
movieActors: '',
imdbId: '',
}
};
}
onSubmit = movie => {
this.setState({movie}), this.fetchMovie);
};
componentWillMount() {
this.fetchMovie();
}
fetchMovie() {
this.APIURL = `http://www.omdbapi.com/? s=${this.state.movie}&apikey=${API_KEY}`;
console.log(this.APIURL);
fetch(this.APIURL)
.then(resp => resp.json())
.then(data => {
const movies = data.Search;
this.setState(state => ({
...state,
movies
}));
});
}

There is no reason to get whole list of movies every time you update state. In componentWillMount() you can do initial fetch of movies and then on submit you can just update the state with new movie. If needed you can call React.forceUpdate() to trigger render method.
constructor(props) {
super(props);
this.state = {
movie: "Interstellar",
movies: [],
newMovieParsed: {
movieTitle: '',
moviePosters: '',
moviePlot: '',
movieGenre: '',
movieBoxOffice: '',
movieRatings: [],
movieActors: '',
imdbId: '',
}
};
}
componentWillMount() {
this.getMovies();
});
getMovies = () => {
this.APIURL = `http://www.omdbapi.com/? s=${this.state.movie}&apikey=${API_KEY}`;
console.log(this.APIURL);
fetch(this.APIURL)
.then(resp => resp.json())
.then(data => {
const movies = data.Search;
this.setState(state => ({
...state, movies
}));
}
onSubmit = movie => {
this.setState(state => ({ ...state, movie }));
};
}

Related

React pass from parent to child

I'm trying to pass data in React from parent to child , I already managed to set right value from one file to another, but same that information that I passed I need to pass once more again. I will show you some code so you can understand actual problem.
From List.js file I'm taking the right information like
<Products categoryid={item.id}/>
so that same item.id I passed to Products, as you see I have this.props.categoryid which is giving me right information as value to add this item as you see, and it looks like
import React, { Component } from 'react'
import { getProducts, addItem, deleteItem, updateItem } from './ProductFunctions'
class Products extends Component {
constructor() {
super()
this.state = {
id: '',
title: '',
price: '',
off_price: '',
category_id: '',
arttitle: '',
artbody: '',
editDisabled: false,
items: []
}
this.onSubmit = this.onSubmit.bind(this)
this.onChange = this.onChange.bind(this)
}
componentDidMount() {
this.getAll()
}
onChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
getAll = () => {
getProducts().then(data => {
this.setState(
{
title: '',
price: '',
off_price: '',
category_id: this.props.categoryid,
items: [...data]
},
() => {
console.log(this.state.items)
}
)
})
}
So the real problem is how to pass this this.props.categoryid as a category_id to getProducts function in ProductFunctions.js so I can get list from ?
export const getProducts = category_id => {
return axios
.get('/api/products/${category_id}', {
headers: { 'Content-Type': 'application/json' }
})
.then(res => {
return res.data
})
}
It seems you forgot to use `` and instead used '' in the getProducts function in ProductFunctions.js, so let's correct that.
export const getProducts = category_id => {
return axios
.get(`/api/products/${category_id}`, {
headers: { "Content-Type": "application/json" }
})
.then(res => {
return res.data;
});
};
Now, just pass the categoryid you obtained from props to the getProducts in the getAll method, when its invoked. (As per what the exported function expects in ProductFunctions.js
getAll = () => {
const { categoryid } = this.props;
getProducts(categoryid).then(data => {
this.setState(
{
title: "",
price: "",
off_price: "",
category_id: categoryid,
items: [...data]
},
() => {
console.log(this.state.items);
}
);
});
};
Access the prop within getAll function
getAll = () => {
getProducts(this.props.categoryid).then(data => {
this.setState({
title: '',
price: '',
off_price: '',
category_id: this.props.categoryid,
items: [...data]
},
() => {
console.log(this.state.items)
}
)
})
}

My console.log is in the wrong place but I do not understand why

So my console.log prints as if I am one step behind on my onSubmit calls, but when I check the React web tools on Chrome I see that my state is up to date. Can someone please explain to me what I am doing wrong here? I believe it is a misunderstanding up asynchronous and synchronous methods, but could really use a good explanation on this.
import React, { Component } from 'react';
import TodoInput from './todo-input';
class App extends Component {
constructor(props){
super(props);
this.state = {
todos: [],
inputValue: ''
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
handleChange = (e) => {
e.preventDefault();
this.setState({
inputValue: e.target.value
});
}
handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
title: this.state.inputValue,
id: Date.now,
done: false
};
this.setState((prevState) => ({
todos: [...prevState.todos, newTodo]
}));
this.setState({inputValue: ''});
console.log(this.state.todos);
}
render() {
const mappedTodos = this.state.todos.map((todo, index) =>
<div key={index}>
{todo.title}
</div>
)
return (
<div>
<TodoInput
value={this.state.inputValue}
onChange={this.handleChange}
onSubmit={this.handleSubmit}
/>
{mappedTodos}
</div>
);
}
}
export default App;
This should console log after state update.
this.setState((prevState) => ({
todos: [...prevState.todos, newTodo]
}), () => {
console.log(this.state.todos)
});
Yes the setState() is an asynchronous call, so your console.log() is returning the state as if none of your setState() calls were made. However, this can be solved using the setState callback function argument to produce your expected output:
handleSubmit = (e) => {
e.preventDefault();
const newTodo = {
title: this.state.inputValue,
id: Date.now,
done: false
};
this.setState((prevState) => ({
todos: [...prevState.todos, newTodo],
inputValue: ''
}), () => { console.log(this.state.todos) });
}

React and checking condition after passing function through props

I'm fighting with my app since long time and slowly there is progress however I have still problem with one thing
I want to pass function thought props from Form Component to List component, after that I wish to check if button add was clicked if yes then I wish to launch function getMovie() inside List component and send another request to json database. with edit and remove it works as there are in same component, with adding button it is a bit more tricky.
the problem is that if I write just
else if (this.props.addClick) {
this.getMovie();
}
it's keep sending requests to database over and over
below is my code
Form Component
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '',
type: '',
description: '',
id: '',
movies: [],
errors: "",
}
}
handleSubmit = e => {
e.preventDefault()
const url = `http://localhost:3000/movies/`;
if (this.state.name != "" && this.state.type != "" && this.state.description != "") {
axios
.post(url, {
name: this.state.name,
type: this.state.type,
description: this.state.description,
id: this.state.id,
})
.then(res => {
this.setState({
movies: [this.state.name, this.state.type, this.state.description, this.state.id]
})
})
.then(this.setState({
isButtonRemoveClicked: true
}))
}
else {
this.setState({
errors:"Please, Fill all forms above"
})
}
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="Movie" onChange={this.handleChangeOne}/>
<input type="text" placeholder="Type of movie" onChange={this.handleChangeTwo}/>
<textarea placeholder="Description of the movie"
onChange={this.handleChangeThree}></textarea>
<input id="addMovie" type="submit" value="Add movie" ></input>
<p>{this.state.errors}</p>
</form>
<List removeClick={this.handleRemove} editClick={this.editMovie} addClick={this.handleSubmit}/>
</div>
)
List Component
class List extends React.Component {
constructor(props) {
super(props)
this.state = {
movies: [],
isButtonRemoveClicked: false,
}
}
componentDidMount() {
this.getMovie()
}
componentDidUpdate() {
if (this.state.isButtonRemoveClicked === true) {
this.getMovie();
this.timer = setTimeout(() => {
this.setState({
isButtonRemoveClicked: false
})
}, 10)
}
else if (this.props.addClick === true) {
this.getMovie();
}
}
componentWillUnmount() {
clearTimeout(this.timer)
}
getMovie = () => {
const url = `http://localhost:3000/movies`;
axios
.get(url)
.then(res => {
const movies = res.data;
this.setState({
movies: movies,
})
})
.catch((err) => {
console.log(err);
})
}
There is nothing magical ;)
You're start loading data from componentDidUpdate() ... data loads, componentDidUpdate is fired again, again...
Don't handle events this way.
If your main objective is to call function in child component from parent component, then you can use refs.
Example in your code :-
class Form extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '',
type: '',
description: '',
id: '',
movies: [],
errors: "",
}
}
handleSubmit = e => {
e.preventDefault()
const url = `http://localhost:3000/movies/`;
if (this.state.name != "" && this.state.type != "" && this.state.description != "") {
axios
.post(url, {
name: this.state.name,
type: this.state.type,
description: this.state.description,
id: this.state.id,
})
.then(res => {
this.setState({
movies: [this.state.name, this.state.type, this.state.description, this.state.id]
})
})
.then(
this.list.getMovie(); // call child function here
this.setState({
isButtonRemoveClicked: true
}))
}
else {
this.setState({
errors:"Please, Fill all forms above"
})
}
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="Movie" onChange={this.handleChangeOne}/>
<input type="text" placeholder="Type of movie" onChange={this.handleChangeTwo}/>
<textarea placeholder="Description of the movie"
onChange={this.handleChangeThree}></textarea>
<input id="addMovie" type="submit" value="Add movie" ></input>
<p>{this.state.errors}</p>
</form>
<List
ref={list => this.list=list } // Create ref here
removeClick={this.handleRemove}
editClick={this.editMovie}
addClick={this.handleSubmit}/>
</div>
)
And in list component no need to use componentDidUpdate getMovie() call.
class List extends React.Component {
constructor(props) {
super(props)
this.state = {
movies: [],
isButtonRemoveClicked: false,
}
}
componentDidMount() {
this.getMovie()
}
getMovie = () => {
const url = `http://localhost:3000/movies`;
axios
.get(url)
.then(res => {
const movies = res.data;
this.setState({
movies: movies,
})
})
.catch((err) => {
console.log(err);
})
}
I think you are handling events in an overcomplicated manner. Why don't you lift props from inside the List component and just trigger the desired behaviour in the Form?. For example:
class List extends React.Component {
handleAddClick() {
this.props.onAddClick()
}
handleEditClick() {
this.props.onEditClick()
}
handleRemoveClick() {
this.props.onRemoveClick()
}
render() {
return (
<div>
<button onClick={() => this.handleAddClick()}>Add</button>
<button onClick={() => this.handleEditClick()}> Edit</button>
<button onClick={() => this.handleRemoveClick()} > Remove</button>
</div>
})
}
and
class Form extends React.Component {
getMovie() {
// Make AXIOS request
}
handleAdd() {
this.getMovie();
}
handleRemove() {
// REMOVE CODE
}
handleEdit() {
// EDIT CODE
}
render() {
<form>
{/* Form elements */}
<List
onAddClick={() => this.handleAdd()}
onRemoveClick={() => this.handleRemove()}
onEditClick={() => this.handleEdit()}
/>
</form>
}
}

React Cannot read property state of undefined with API call

I'm trying to get a simple API call working, where the component calls the API as its mounting and sets the state to be rendered. But when I try to get the state to change an object in it, it says that the state is undefined.
TypeError: Cannot read property 'state' of undefined
class SpellGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
spacing: '16',
username: 'admin',
password: 'notpassword',
description: '',
remember: false,
spell: {
name: '',
school: '',
},
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.mapApiToState = this.mapApiToState.bind(this);
}
mapApiToState() {
// I've tried with all of the axios code in here.
}
componentDidMount() {
axios
.get("http://localhost:8000/api/spells/1")
.then(function(response) {
console.log('response', response);
let fields = response.data[0].fields;
// THIS IS THE LINE THAT IS ERRORING
let spell = Object.assign({}, this.state.spell);
spell.name = fields.Name;
spell.school = fields.School;
console.log('spell', spell);
this.setState({spell});
console.log('state.spell', this.state.spell);
//console.log('state', this.state);
})
.catch(function(error) {
console.log(error);
});
console.log('state', this.state);
}
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
onSubmit = (event) => {
event.preventDefault();
this.props.onSubmit(this.state.username, this.state.password)
};
handleSubmit(e) {
console.log('Form state: ', this.state);
e.preventDefault();
}
render() {
const {classes, theme} = this.props;
const { spacing } = this.state;
return (
<div>{this.state.spell.name}</div>
);
}
} export default withStyles(styles, { withTheme: true })(SpellGrid);
If you are using this, you will need to be carefull in which function scope you're in:
axios
.get("http://localhost:8000/api/spells/1")
.then(response => {
// Since the `response` is now an arrow function, we still
// get access to the original `this`
let fields = response.data[0].fields;
let spell = Object.assign({}, this.state.spell);
spell.name = fields.Name;
spell.school = fields.School;
this.setState({
spell
});
})
.catch(error => {
console.log(error);
});

Trouble getting child component to update in Redux

I'm building a simple CRUD note app and I'm having issues getting the child components to update after simple POST and DELETE api calls to create and delete notes.
Here's the parent component with a simple form and a child component <NotepadsShowView /> to render the submitted notes.
class AuthenticatedHomeView extends Component {
_handleSubmit(e) {
e.preventDefault()
const { dispatch } = this.props
const data = {
title: this.refs.title.value,
description: this.refs.description.value,
private: this.refs.private.checked
}
dispatch(Actions.createNotepad(this.props.currentUser.id, data))
this._resetForm()
}
_resetForm() {
this.refs.title.value = ''
this.refs.description.value = ''
this.refs.private.checked = true
}
render() {
return (
<div>
<form onSubmit={::this._handleSubmit}>
{/* form removed for clarity */}
</form>
<NotepadsShowView/>
</div>
)
}
}
and the NotepadsShowView child component:
class NotepadsShowView extends Component {
componentWillMount() {
const { dispatch, currentUser } = this.props
dispatch(Actions.fetchNotepads(currentUser.id))
}
_renderEachOwnedNotepad() {
const { ownedNotepads } = this.props
return ownedNotepads.map((notepad, i) => {
return <NotepadShowView key={notepad.id} {...notepad} {...this.props}/>
})
}
render() {
const { isFetchingNotepads } = this.props
const notepads = this._renderEachOwnedNotepad()
if (isFetchingNotepads || notepads.length == 0) return null
return (
<ul className="notepads-container">
{notepads}
</ul>
)
}
}
const mapStateToProps = (state) => ({
isFetchingNotepads: state.notepads.fetching,
currentUser: state.session.currentUser,
ownedNotepads: state.notepads.ownedNotepads,
sharedNotepads: state.notepads.sharedNotepads
})
export default connect(mapStateToProps)(NotepadsShowView)
Here is the action creator:
const Actions = {
createNotepad: (userId, data) => {
return dispatch => {
httpPost(`/api/v1/users/${userId}/notepads`, {data: data})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_CREATED,
notepad: data
})
})
.catch(error => {
error.response.json()
.then(json => {
dispatch({
type: CONSTANTS.NOTEPADS_CREATE_ERROR,
errors: json.errors,
})
})
})
}
},
fetchNotepads: (userId) => {
return dispatch => {
dispatch({
type: CONSTANTS.NOTEPADS_FETCHING
})
httpGet(`/api/v1/users/${userId}/notepads`, {id: userId})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_RECEIVED,
notepads: data.notepads
})
})
.catch(error => {
error.response.json()
.then(json => {
dispatch({
type: CONSTANTS.NOTEPADS_ERRORS,
errors: json.error
})
})
})
}
},
deleteNotepad: (userId, notepadId) => {
return dispatch => {
httpDelete(`api/v1/users/${userId}/notepads/${notepadId}`, {id: notepadId})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_OWNED_DELETE,
id: notepadId
})
})
}
},
}
Here is the reducer:
const initialState = {
ownedNotepads: [],
fetching: true,
}
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case CONSTANTS.NOTEPADS_FETCHING:
return {
...state,
fetching: true,
}
case CONSTANTS.NOTEPADS_RECEIVED:
return {
...state,
fetching: false,
ownedNotepads: action.notepads
}
case CONSTANTS.NOTEPADS_CREATED:
return {
...state,
ownedNotepads: [
...state.ownedNotepads,
{
id: action.id,
title: action.title,
description: action.description,
private: action.private
}
]
}
case CONSTANTS.NOTEPADS_OWNED_DELETE:
const index = state.ownedNotepads.findIndex(note => note.id === action.id)
return {
...state,
ownedNotepads: [
...state.ownedNotepads,
state.ownedNotepads.slice(0, index),
state.ownedNotepads.slice(index + 1)
]
}
default:
return state
}
}
A user submits a new notepad which triggers an POST api call. Server returns the new notepad and the reducer adds the notepad to the Redux state. No issues here. However, when a notepad is created the notepad props are undefined and no new notepads are being shown in the child UI components. They don't know of the update and I assume it's because I'm not handling the state update.
I am using componentWillMount (cWM) above to fetch the updated notepads state before the initial render. I'm assuming I should use componentWillReceiveProps but I understand there will be an infinite loop if I dispatch the fetchNotepads action because the dispatch in cWM will run again.
My question is how do I update the component props when the Redux state changes? Do I have to use component state? What about the lifecycle methods?

Categories