ReactJs setState skips first letter - javascript

Whenever i type something is search bar update the state, it skips the first letter. For example, if i write "asdf" it only shows "sdf".
I tried console.log before this line of code
this.props.newQuery(this.state.newSearchQuery);
and everything was working fine.
Please check the below code of App.js and SearchBar.js
Thanks
App.js
import React from 'react';
import SearchBar from './components/SearchBar';
class App extends React.Component {
constructor(){
super();
this.state = {
searchQuery: '',
fetchedData: []
};
}
newQuery(query){
this.setState({
searchQuery: query
});
}
onSearch(){
const userInput = this.state.searchQuery;
if(userInput !== '' && userInput !== ' '){
const API_KEY = `https://pokeapi.co/api/v2/pokemon/${userInput}`;
fetch(API_KEY, {
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(result => result.json())
.then(data => this.setState({ fetchedData: data.results }));
console.log('res', this.state.fetchedData);
}
}
render(){
return(
<div className="App">
<h2>Search Pokemos by Types</h2>
<hr />
<SearchBar onSearch={this.onSearch.bind(this)} newQuery={this.newQuery.bind(this)} />
</div>
);
}
}
export default App;
SearchBar.js
import React from 'react';
class SearchBar extends React.Component {
constructor(props){
super(props);
this.state = {
newSearchQuery: '' //this blank value get executed first when i console.log
}
}
searchInput(event){
this.setState({
newSearchQuery: event.target.value
});
this.props.newQuery(this.state.newSearchQuery);
console.log(this.state.newSearchQuery); // if i log "asdf", state on top "newSearchQuery" skips the first letter, a and shows "sdf" only.
}
render(){
return(
<div className="input-group">
<input onChange={this.searchInput.bind(this)} className="form-control" placeholder="[eg. Ditto, Cheri, etc]" />
<button onClick={this.props.onSearch} className="btn btn-success">Search</button>
</div>
);
}
}
export default SearchBar;

The console.log doesnot log as expected because the setState() method is not always executed as its called. According to Docs
State Updates May Be Asynchronous:Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
So when you are logging this console.log(this.state.newSearchQuery); after the setState the state is not actually changed so thats why it logs unexpected

I'm a bit confused as to the redundant state between the two components. What I think I would do (if I wasn't using something like mobx) is keep the state on the parent component and pass the handleChange and handleSearch functions to the <Search /> component. It would look something like...
I threw together a codesandbox for you: https://codesandbox.io/s/n1ryz4rzwl
APP Component
import React, { Component } from 'react';
import SearchBar from './components/SearchBar'
class App extends Component {
constructor() {
super()
this.state = {
searchQuery: '',
fetchedData: []
}
}
handleChangeQuery = event => this.setState({searchQuery: event.target.value})
handleSearch = () => {
const {searchQuery} = this.state,
API_KEY = `https://pokeapi.co/api/v2/pokemon/${searchQuery}`;
fetch(API_KEY, {
method: 'GET',
headers: {
Accept: 'application/json'
}
})
.then(result => result.json())
.then(data => this.setState({ fetchedData: data.results }));
}
render() {
const {searchQuery} = this.state
return (
<div className="App">
<h2>Search Pokemos by Types</h2>
<hr />
<SearchBar
value={searchQuery}
handleChangeQuery={this.handleChangeQuery}
handleSearch={this.handleSearch}
/>
// Show fetchedData results here
</div>
)
}
}
export default App
SearchBar Component - This COULD be a stateless functional component
import React from 'react'
const SearchBar = ({value, handleChangeQuery, handleSearch}) => {
return (
<div className="input-group">
<input
onChange={handleChangeQuery}
value={value}
className="form-control"
placeholder="[eg. Ditto, Cheri, etc]"
/>
<button onClick={handleSearch} className="btn btn-success">Search</button>
</div>
)
}
export default SearchBar
The reasoning behind the strange missing character has been described by the other comments - as the this.setState() may be async. However, this.setState() does have a callback function that can be used to confirm the change if you want to test with that. It looks something like:
this.setState({key: value}, () => {
// State has been set
console.log(this.state.key)
})

Related

React: trigger event in child component by click on parent

Context:
I want to trigger an event in a parents child component by an onClick on the parent element
Code:
Parent PlantContainer:
import React from "react";
import ClipLoader from "react-spinners/ClipLoader";
import Box from '#material-ui/core/Box';
import ShowMetric from '../showMetric';
export default class PlantContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
};
}
render() {
return (
<Box>
<h2>{this.props.plantName}</h2>
<ShowMetric
setting={this.props.plantName + ".moisture"}
unit="%">Moisture:</ShowMetric>
<ShowMetric
setting={this.props.plantName + ".conductivity"}
unit="%">Fertility:</ShowMetric>
</Box>
);
}
}
Child ShowMetric:
import React from "react";
import ClipLoader from "react-spinners/ClipLoader";
import resolvePath from 'object-resolve-path';
export default class ShowMetric extends React.Component {
constructor(props) {
super(props);
this.getData = this.getData.bind(this);
this.state = {
isLoading: false,
reading: 0,
};
}
getData() {
this.setState({ isLoading: true });
fetch(URL_HERE, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
},
})
.then(function (response) {
return response.json();
})
.then((json) =>
this.setState({
reading: resolvePath(json, this.props.setting),
isLoading: false,
})
);
}
componentDidMount() {
this.getData();
}
render() {
if (this.state.isLoading) {
return <ClipLoader />;
}
return (
<div onClick={this.getData}>
{this.props.children + " "}
<nobr>{`${this.state.reading.toFixed(1)} ${this.props.unit}`}</nobr>
</div>
);
}
}
Main App.js:
import './App.css';
import React from 'react';
import Container from '#material-ui/core/Container';
import Box from '#material-ui/core/Box';
import PlantContainer from './components/plantContainer';
function App() {
return (
<div className="App">
<Container maxWidth="md">
<Box className="flexBox">
<PlantContainer plantName="Plant_1"/>
<PlantContainer plantName="Plant_2"/>
</Box>
</Container>
</div>
);
}
export default App;
Problem
The above code works as expected, as <ShowMetric/> shows the information and reloads when I click on it.
Now I want to reload all <ShowMetric/> Elements in PlantContainer (maybe trigger the getData() function for each of them) when I click the <H2> Element of PlantContainer.
I tried to find ways how to pass down events or informations to children, but since props can't change at runtime (?) and I don't think a reference would be the best way here, I am a bit at lost on how to implement this.
And as this is my very first react web App and endeavour into this framework please call out any fishy thing you can find in the code.
I think the more elegant way to do this would be to store all the data in the parent component and pass it down to the children through the props.
Here is a possible solution (I used function components as it should be privileged over the class components) :
PlantContainer
function fetchData() {
return fetch(URL_HERE, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
},
})
.then(response => response.json());
}
export default function PlantContainer(props) {
const [data, setData] = React.useState({
isLoading: false,
'moisture': 0,
'conductivity': 0
});
function loadData() {
setData({...data, isLoading: true});
fetchData().then(json => {
setData({
isLoading: false,
'moisture': resolvePath(json, `${props.plantName}.moisture`),
'conductivity': resolvePath(json, `${props.plantName}.conductivity`)
});
});
}
React.useEffect(loadData, []);
return (
<Box>
<h2 onClick={loadData}>{props.plantName}</h2>
{data.isLoading && <ClipLoader/>}
{!data.isLoading && (
<ShowMetric
reading={data['moisture']}
unit="%">Moisture:</ShowMetric>
<ShowMetric
reading={data['conductivity']}
unit="%">Fertility:</ShowMetric>
)}
</Box>
);
}
ShowMetric
export default function ShowMetric(props) {
return (
<div>
{props.children + " "}
<nobr>{`${props.reading.toFixed(1)} ${props.unit}`}</nobr>
</div>
);
}
As you can retrieve all the data by calling the service a single time, it seems to be useless to reload only one metric, so I only give to opportunity to reload both metrics by clicking on the h2 element.
The useImperativeHandle hook is perfect to allow child components and refs.
Fully working example with Typescript support too!:
//Child Component
//Create your ref types here
export type RefHandler = {
pressAlert: () => void;
inputRef: RefObject<HTMLInputElement>;
};
const Child = forwardRef<RefHandler, Props>((props, ref) => {
const submitRef = useRef<HTMLButtonElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
//Initialise your refs here
useImperativeHandle(ref, () => ({
inputRef: inputRef,
pressAlert: () => submitRef?.current?.click()
}));
return (
<div>
<p>Child Component</p>
<input type="text" value="lorem ipsum" ref={inputRef} />
<br />
<button onClick={() => alert("Alert pressed")} ref={submitRef}>
Alert
</button>
</div>
);
});
//Parent
export default function Parent() {
const childRef = useRef<RefHandler>(null);
return (
<>
<p>Parent</p>
<button
onClick={() => {
alert(childRef?.current?.inputRef?.current?.value);
}}
>
Read child input
</button>
<button onClick={() => childRef?.current?.pressAlert()}>
Press child button
</button>
<hr />
<Child ref={childRef} />
</>
);
}

React <Redirect> after transition not working

I have the following component which has a redirection route after an animation is finished, like so:
Menus.jsx
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
select: 'espresso',
isLoading: false,
redirect: false
};
gotoCoffee = () => {
this.setState({isLoading:true})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000) //Replace this time with your animation time
}
renderCoffee = () => {
if (this.state.redirect) {
return (<Redirect to={`/coffee/${this.state.select}`} />)
}
}
render(){
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={this.gotoCoffee}
style={{textDecoration:'underline',cursor:'pointer'}}>
<strong><font color="#C86428">{this.state.coffees[0]}</font></strong></div>
</div>
</div>
);
}
}
export default withRouter(Menus);
The animation called onCLick:
Brewing.jsx
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import './css/mug.css'
class Brewing extends Component {
constructor (props) {
super(props);
};
render() {
return (
<div>
<div className="cup">
<div className="coffee"></div>
</div>
<div className="smoke"></div>
</div>
);
}
}
export default withRouter(Brewing);
And here redirected route component:
Coffee.jsx
class Coffees extends Component{
constructor (props) {
super(props);
this.state = {
select:'',
template:''
};
};
componentDidMount() {
if (this.props.isAuthenticated) {
this.getCoffee();
}
};
getCoffee(event) {
//const {userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/coffee/espresso`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data.data)
this.setState({
template: res.data.data[0].content
})
})
.catch((error) => { console.log(error); });
};
render(){
var __html = this.state.template;
var template = { __html: __html };
return (
<div id="parent">
<h1 className="title is-1"><font color="#C86428">{this.state.select} playlist</font></h1>
<hr/><br/>
<div dangerouslySetInnerHTML={template}/>
</div>
);
}
}
export default withRouter(Coffees);
but <Redirect> in Menus.jsx is not working....url changes at browser but nothing happens; only if I refresh the browser /coffee is sucessfully mounted.
What I actually need to happen:
render Menu
click on a link
click renders an animation
when animation is done, after 5 seconds,
<Redirect> to /coffee
what am I missing?
When you say url changes at browser but nothing happens; only if I refresh the browser /coffee is sucessfully mounted.
This might be the issue with your Routes.
When you redirect to path /coffee/${this.state.select}, you should have Route to handle this path.
<Route path="/coffee/:select?" render={() => ( <Coffees isAuthenticated={true}/> )}/>
Note: Be aware of adding exact prop to Route. When you add exact prop it means your path should match exactly with all the provided params.
You need to call getCoffee function in also componentDidUpdate function.
componentDidMount() {
if (this.props.isAuthenticated) {
this.getCoffee();
}
};
componentDidUpdate() {
if (this.props.isAuthenticated) {
this.getCoffee();
}
};
Your Redirect should be inside the render().
render(){
if(this.state.redirect) {
return(<Redirect to={`/coffee/${this.state.select}`} />)
} else {
return (
<div>
...your component
</div> );
}
}
Note that this way you shouldn't need your renderCoffee() function.
I'm on mobile so i wasn't able to test if it works. Let me know if this works for you.
It seems your Menu component construtor has no closing bracket.
...
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
select: 'espresso',
isLoading: false,
redirect: false
};
} // did you miss this?
gotoCoffee = () => {
...

Why is my component not rerendering after form submission and setting state with Firebase?

I am building a component that is supposed to rerender after a form submission, and it sends the database perfectly fine, but I want the component to rerender after it submits and not having to refresh the page.
I have heard React rerenders after state is changed, and I have tried to set the state of the form inputs on submit.
import React, { Component } from 'react';
import { withFirebase } from '../Firebase';
import { FirebaseContext } from '../Firebase';
import { Link } from 'react-router-dom';
import AddNew from '../AddNew';
import { compose } from 'recompose';
import Firebase from '../Firebase';
import * as ROUTES from '../../constants/routes';
import { throwStatement, thisExpression, tsExpressionWithTypeArguments } from '#babel/types';
class Home extends Component {
constructor(props) {
super(props)
this.state = {
loading: false,
isHidden: false,
name: '',
image: '',
data: []
}
this.baseState = this.state
this.toggleAddNew = this.toggleAddNew.bind(this);
}
getPosts() {
this.props.firebase.getClients().then(snapshot => {
this.setState({
data: snapshot.docs
})
});
}
// Component lifecycle methods
componentWillMount() {
this.getPosts()
}
componentDidUpdate(){
console.log('updated')
}
toggleAddNew() {
this.setState({
isHidden: !this.state.isHidden
})
}
updateInput = e => {
this.setState({
[e.target.name]: e.target.value
});
}
resetForm = () => {
this.setState(this.baseState)
}
deletePost = (id) => {
this.props.firebase.deleteClient(id);
}
addClient = e => {
e.preventDefault();
this.props.firebase.addClient().add({
name: this.state.name,
image: this.state.image
})
this.setState({
name: '',
image: ''
});
this.resetForm();
};
render() {
const renderPosts = this.state.data.map((item) => (
<li data-id={item.id} className="client-wrapper col-sm-4">
<button onClick={() => this.deletePost(item.id)}>X</button>
<Link to={`/dates/${item.id}`}>
<h2>{item.data().name}</h2>
</Link>
<Link to={`/dates/${item.id}`}>
<img src={item.data().image} />
</Link>
</li>
));
return (
<div>
<ul id="client-list" className="row">{renderPosts}</ul>
<button onClick={this.toggleAddNew.bind(this)}>Add New</button>
{this.state.isHidden ?
<div id="add-new-form-wrapper">
<button onClick={this.toggleAddNew.bind(this)} id="x-add-new">X</button>
<form onSubmit={this.addClient.bind(this)} id="add-new-form">
<input type="text" name="name" placeholder="Name" onChange={this.updateInput} value={this.state.name} />
<input type="text" name="image" placeholder="Image" onChange={this.updateInput} value={this.state.image} />
<button type="submit">Submit</button>
</form>
</div> :
''}
</div>
)
}
}
export default compose(
withFirebase,
)(Home);
This
this.baseState = this.state
only makes a copy of object reference, not a copy of state object (with property values).
When we have a reference copy
resetForm = () => {
this.setState(this.baseState)
}
can work like state = state, does nothing.
The copy of object (with current property values) can be done (f.e.) this way:
this.baseState = {...this.state}
With this small fix it should work ...
... if not, try
resetForm = () => {
this.setState({...this.baseState})
}
You can also update some state field with current time to force rerender or simply call this.forceUpdate() (see docs).
BTW - resetForm shouldn't overwrite data. Luckily we have a copy of data object reference in baseState ;)

TypeError: _this2.setState is not a function

I don't understand why i'm getting this error. How could i solve it and most importantly why am i getting it?
I'm getting the correct response back from the API but i also get the error right after a make a call.
class App extends Component {
constructor(props) {
super(props);
this.state = {
movies: []
};
}
videoSearch(term) {
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${APIkey}&query=${term}&page=1`)
.then(response => this.setState({movies: response.data.results}))
.catch(err => console.log(err))
};
render() {
return (
<div className="App">
<SearchBar onSearchTermChange={this.videoSearch} />
</div>
);
}
}
export default App;
import React, {Component} from 'react';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = { term: "" };
}
render() {
return (
<div className="search-bar">
<input
value={this.state.term}
onChange={event => this.onInputChange(event.target.value)}
/>
</div>
);
}
onInputChange(term) {
this.setState({ term });
this.props.onSearchTermChange(term);
}
}
export default SearchBar;
Wild guess base on a mass quantity of these type of questions :)
Try:
constructor(props) {
super(props);
this.state = {
movies: []
};
this.videoSearch = this.videoSearch.bind(this);
}
Tell me if it works. If not I'll delete the answer.
I suspect that this is called for a different object than your App component.
Adding bind in constructor works but can affect readability at times.
this.videoSearch = this.videoSearch.bind(this);
Instead, you can also make the videoSearch an arrow function.
videoSearch = term => {
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${APIkey}&query=${term}&page=1`)
.then(response => this.setState({movies: response.data.results}))
.catch(err => console.log(err))
};

How to add to state array in React

I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
}
//The function triggered by button which sends the task the user has entered to the tasks array state:
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein', tasks:
this.state.inputText
})
}
render() {
//Where User enters task:
return (
<div className="App">
<main>
<div className="enterTask">
<input type="text" className="inputclass" id="textfield1"
placeholder='Enter a task.'
onChange={event => this.setState({
inputText: event.target.value })}
onKeyPress={event => {
if(event.key === 'Enter') {
this.addItem();
}
}}
/>
<br />
<br />
<button className="button"
onClick={() => this.addItem()} data-
toggle='fadein' data-target='list'>+
</button>
</div>
<!-- Where tasks will appear: -->
<div className="log">
<p className='list'>
<span class={this.state.triggerAnimation}>
{this.state.tasks}
</span>
</p>
<button className="button">-</button>
</div>
</main>
</div>
)
}
}
export default App;
However I don't know how to update the tasks state (which is an array) to push the new tasks.
Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:
this.setState(prevState => ({
tasks: [...prevState.tasks, newTask]
}));
Seems like what you want is this..
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein',
tasks: this.state.tasks.concat(this.state.inputText)})
}
You can use .concat method to create copy of your array with new data:
addTask() {
this.setState({tasks: this.state.tasks.concat(["new value"])})
}
You also need to bind this to addTask in your constructor:
this.addTask = this.addTask.bind(this)
See my example:
https://jsfiddle.net/69z2wepo/103069/
Documentation: https://reactjs.org/docs/react-component.html#setstate
try this
import React from 'react';
class Todo extends React.Component {
constructor(props) {
super();
this.state = {
value: '',
items: []
}
}
onChange = e => this.setState({ value: e.target.value })
onEnter = e => {
if(e.charCode !== 13) return;
this.addItem();
};
onClick = e => {
this.addItem()
};
addItem = () => {
const { value } = this.state;
if(!!value.trim()) return;
this.setState(prev => ({ items: [...prev.items, value], value: '' }))
};
render() {
const { value } = this.state
return (
<div>
<div>
<input
type="text"
value={value}
name="abc"
onChange={this.onChange}
onKeyPress={this.onEnter}
/>
</div>
<button onClick={this.onClick}>Add</button>
</div>
)
}
}
FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.
setState({tasks:this.state.tasks.concat([this.state.inputText])})
Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.
Edit actually put the right code here now...
With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyPress = this.onInputKeyPress.bind(this);
this.addItem = this.addItem.bind(this);
}
onInputChange(e) {
this.setState({ inputText: e.target.value });
}
onInputKeyPress(e) {
if (e.key === "Enter") {
this.addItem();
}
}
addItem() {
const itemToAdd = this.state.inputText;
const tasks = this.state.tasks;
this.setState({
inputText: "",
tasks: tasks.concat(itemToAdd);
});
}
render() {
const { inputText } = this.state;
return(
<div>
<input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.'
value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
<br />
<br />
<button className="button" onClick={this.addItem} data-
toggle='fadein' data-target='list'>+</button>
</div>
);
}
}
Notice how input state is controlled via component state

Categories

Resources