Updated code
Initialized constructor and placed filter and loadOptions in class render method.
Still showing error saying that this.state.cars.filter is not a function.
import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: null,
cars: []
};
}
componentDidMount() {
fetch(url)
.then(res => {
this.setState(prevState => ({...prevState, cars: res}))
})
}
handleInputChange = (newValue) => {
const inputValue = newValue.replace(/\W/g, '');
this.setState({ inputValue });
return inputValue;
};
render() {
const filterCars = (inputValue) => {
return this.state.cars.filter((i) =>
i.label.toLowerCase().includes(inputValue.toLowerCase())
);
};
const loadOptions = (
inputValue,
callback) => {
setTimeout(() => {
callback(filterCars(inputValue));
}, 1000);
};
return (
<div>
<AsyncSelect
cacheOptions
loadOptions={loadOptions}
defaultOptions
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
Example code of json file that Im fetching data from
[{"make":"KIA","link":"\/images\/image.jpg"},{"make":"BMW","link":"\/images\/image.jpg"}]
There are a couple of issues here. First is that you should declare state inside of a constructor, so instead of declaring state like that, do the following:
constructor(props) {
super(props)
this.state = {inputValue: '', cars: []}
}
Then you need to deal with your state mutations. Whenever you call setState, you are essentially giving it a new object which it sets the state to. You do not reassign the properties, you return a new object.
To resolve this reassignment issue, ES6 has introduced the spread operator which was not specifically designed to deal with mutations but it helps a lot.
Essentially, whenever you want to create a copy of an object, you don't do
let a = b
instead you do
let a = {...b}
With this change, changes on A will not reflect on B. So you make a copy instead of a duplicate.
How you can use this to avoid state mutation?
Whenever you call setState, make sure to first spread the rest of the state accross the new one before making any property changes:
setState(prevState => ({...prevState, cars: res}))
This way, you do not essentially remove the inputValue from your state object which can cause undefined issues.
Back to the main issue, your function filterCars is located outside of your class and in there you are trying to access this.state. Move the filterCars function inside of the class and your error will be resolved but if you don't resolve the mutation issues, it will not work as expected.
Related
class NewComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
obj: [],
externalObj: [],
};
}
getFunc = (external) => {
...
arr = arr.filter(a => a.toLowerCase().includes('eg')
this.setState({ obj: arr });
return arr.map((Id) => {
return <Legend key={Id} id={Id} title={Id}/>;
});
}
render() {
return(
this.getFunc(false);
)
}
}
This is the structure of my React component. I want to set obj in state to that arr to use obj in some other method called completely differently
I am getting this error - Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
I kind of have an idea what is causing this error but I dont know what other thing I can do to achieve what I want - to be able to use this arr in some other method called seperately
You are setting state inside your render function, so when it try to render it, it will set new state and render again, it might cause an infinite loop.
I'm not sure what this line do this.setState({ obj: arr });, but it should be moved somewhere into componentDidMount or componentDidUpdate or wherever you need it.
You can make your filter in one of those lifecycle methods and then render your array from state
UPDATE
In your class add componentDidMount
componentDidMount(){
arr = arr.filter(a => a.toLowerCase().includes('eg')
this.setState({ obj: arr });
}
Then in your getFunc
return this.state.obj && this.state.obj.map((Id) => {
return <Legend key={Id} id={Id} title={Id}/>;
});
I am trying to access a state variable using a string within a React functional component.
For example, I know in Class components I can do the following to access state based on input:
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
inputOne: null
}}
handleBlur(e, name) {
if (!this.state[name]) {
this.setState({
[`${name}Missing`]: true,
});
} else {
this.setState({
[`${name}Missing`]: false,
});
}
render() {
<input onBlur={(e) => this.handleBlur(e, "inputOne")}></input>
}
}
By using this.state[name] within the handleBlur function, I can access the inputOne state value dynamically.
How would I do something similar within a functional component?
Thanks for your time!
You can do that:
import { useState } from 'react';
const Custom = () => {
const [state, setState] = useState({
inputOne: "",
});
const handleBlur = (e, name) => {
if (!state[name]) {
setState({
[`${name}Missing`]: true,
});
} else {
setState({
[`${name}Missing`]: false,
});
}
}
return (
<input onBlur={(e) => handleBlur(e, "inputOne")}></input>
);
};
The only way to do this in a functional component would be to have the same sort of state structure as in the class component - that is, for the state values you want to dynamically access, put them all into an object (or an array).
const [inputs, setInputs] = useState({
inputOne: null
})
And then const someStr = 'inputOne' and inputs[someString] will give you that state value. To set it:
setInputs({
...inputs,
[someStr]: newValue
// or use whatever computed property name you want
});
It's usually recommended to use separate state variables when using functional components - but when you have lots of state variables that you want to dynamically access, using a single object or array can make more sense.
Whenever setState() is called, the component doesn't seem to rerender. As you can see by my comments, the state does in fact change and render seems to be called again, but if I don't add that if statement and simply add a paragraph tag that displays the data it will give me an error. I'm sure I'm missing something simple, but any help is appreciated.
import React from "react";
import axios from "axios";
import { constants } from "../constants/constants";
const { baseURL, apiKey, userName } = constants;
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: []
};
}
componentDidMount() {
let getUserInfo = axios.create({
baseURL,
url: `?
method=user.getinfo&user=${userName}&api_key=${apiKey}&format=json`
});
getUserInfo().then(response => {
let data = response.data;
console.log(data.user.playcount); //logs second, displays correct
this.setState(state => ({
user: data
}));
});
}
render() {
console.log(this.state); //logs first and third, doesn't work on first but does on third
let toReturn;
if (this.state.user.length > 0) {
toReturn = <p>{this.state.user.user.playcount}</p>;
} else {
toReturn = <p>didn't work</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
React LifeCycle function sequence is Constructor and then it calls render method.
In constructor method it initialises the state which is currently empty user array.
Now it calls render() method as this.state.user is an empty array, referencing something out of it gives an error
this.state.user.user.playcount
this will generate an error if you dont have if condition.
After the first render it will call componentDidMount, now you fetch something update state. As setState occurred, render will be called again Now you have something in this.state.user then displaying will happen.
this.state.user.length > 0 is true
Look at this: https://reactjs.org/docs/react-component.html and https://reactjs.org/docs/conditional-rendering.html
You can right in single tag using conditional render like this
<p>{this.state.user.length ? this.state.user.user.playcount : 'loading'}
Hope this helps.
I think your problem might have something to do with the changing shape of the user value. You initialise the value to an empty array, but then—after the fetch is done—you assume it's an object (by using user.user).
Maybe you could simplify the code a bit to look more like the one below?
/* imports */
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null // Make it explicit there's no value at the beginning.
};
}
componentDidMount() {
let getUserInfo = axios.create(/* ... */);
getUserInfo().then(response => {
let data = response.data;
this.setState({ // No need to for a setter function as you dno't rely on the previous state's value.
user: data.user // Assign the user object as the new value.
});
});
}
render() {
let toReturn;
// Since it's now a `null`, you can use a simple existence check.
if (this.state.user) {
// User is now an object, so you can safely refer to its properties.
toReturn = <p>{this.state.user.playcount}</p>;
} else {
toReturn = <p>No data yet.</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
Good Afternoon,
I have a React component that is dynamically rendered in reponse to an API call. I have set the value of one of the elements to a state within the component. During an onClick function (minusOne) this value is supposed to change.
The value is initially rendered successfully based on the state, the function does indeed change the state, however the rendered element stays the same despite the state changing. Does anyone have any ideas of why this might be the case?
If you have any questions, please ask away!
export class Cart extends React.Component {
constructor(props) {
super(props);
this.state={
quantities: []
};
this.minusOne = this.minusOne.bind(this);
}
minusOne(i) {
var self = this;
return function() {
let quantities = self.state.quantities;
if (quantities[i] > 1) {
quantities[i] --;
}
self.setState({
quantities
})
}
}
componentDidMount() {
let cart = this.props.cartTotals;
this.setState({
cart
});
if(cart.lines) {
let cartTotal = [];
let quantities = [];
for (var i = 0; i < cart.lines.length; i++) {
if(cart.lines[i]) {
quantities.push(cart.lines[i].quantity);
}
}
//Initial setting of state
this.setState({
quantities
})
Promise.all(
cart.lines.map(
(cart, i) => axios.get('http://removed.net/article/' + cart.sku)
)
).then(res => {
const allCartItems = res.map((res, i) => {
const data = res.data;
return(
<div key={i} className="cart-item-container">
<img className ="cart-item-picture" src={data.image} name={data.name} />
<div className="cart-item-description">
<p>{data.name}</p>
<p>{data.price.amount} {data.price.currency}</p>
</div>
<div className="cart-item-quantity">
<button onClick={this.minusOne(i)} name="minus">-</button>
//This is the troublesome element
<p className="cart-current-quantity">{this.state.quantities[i]}</p>
<button name="plus">+</button>
</div>
</div>
)
})
this.setState({
allCartItems
})
})
}
}
render() {
return (
{this.state.allCartItems}
);
}
}
Thanks for reading! Any advice will be helpful.
There are two issues:
First, you need to render (including where the onClick is) in render(). ConponentDidMount is only called once and supposed to perform initialization but not render.
Then, there is a problem in minusOne:
quantities points to this.state.quantities. So you are changing the old state, React looks at both the old state and the new one, sees there is no change, and dodesn't render, although the values have changed.
If you will copy this.state.quantities to a new array, like:
newQ = this.state.quantities.slice(0, -1);
Then modify newQ, then do
this.setState({ quantities: newQ });
It should work.
I think you don't need to return a function at minusOne(i) method. Just update the state is enough. You should change the array by specific id.
let quantities = self.state.quantities;
let mutatedQuantities = quantities.map((el, index) => {
return (index === i) ? el - 1 : el;
})
this.setState({quantities: [...mutatedQuantities]})
--- edited ---
I deleted everything I wrote before to make it more concise.
Your problem is that you assign what you want to render to a variable in componentDidMount. This function does only get called once, hence you asigne the variable allCartItems only once. The setState function does not have any effect because it does not trigger componentDidMount and therefore your variable allCartItems does not get reassigned.
What can you do? Well you can do a lot of stuff to enhance your code. First I will let you know about how you can solve your problem and then give you some further improvements
To solve the problem of your component not updating when you call setState you should move your jsx to the render Method. In the componentDidMount you just get all the data you need to render your component and once you have it you can set a flag for example like ready to true. Below you can see an example of how your code could look like.
import React from 'react';
class Cart extends React.Component {
constructor(props) {
super(props);
this.state = {
carts: null,
ready: false,
};
}
componentDidMount() {
fetch('www.google.com').then((carts) => {
this.setState({
carts,
ready: true,
});
});
}
render() {
const myCarts = <h2> Count {this.state.carts} </h2>;
return (
{
this.state.ready
? myCarts
: <h2> Loading... </h2>
}
);
}
}
I made you a demo with a simple counter with some explanations of your case and how you can make it work. You can check it out codesandbox. In the NotWorkingCounter you can see the same problem as in your component of the variable not being updated. In the WorkingCount you can see an example where I implemented what a I wrote above with waiting until your data has arrived and only then render it.
Some more suggestions concerning code:
Those two syntaxes below are identical. One is just a lot more concise.
class Cart extends React.Component {
constructor(props) {
super(props);
this.state = {
carts: null,
ready: false,
};
}
}
class Cart extends React.Component {
state = {
carts: null,
ready: false,
}
}
I would suggest to use arrow function if you want to bind your context. Below you can see your example simplified and an example on how you can achieve the same thing with less syntax.
export class Cart extends React.Component {
constructor(props) {
super(props);
this.minusOne = this.minusOne.bind(this);
}
minusOne(i) {
///
}
}
export class Cart extends React.Component {
constructor(props) {
super(props);
}
minusOne = (i) => {
/// minus one function
}
}
Your minusOne could also be rewritten if you use arrow functions and be a lot smaller, something in the area of
minusOne = (i) => (i) => {
let quant = self.state.quantities[i];
if(quant > 1) {
this.setState({
quantities: quant-1,
})
}
}
In your componentDidMount you call this.setState twice. Every time you call this function your component gets rerender. So what happens in your component is when your mount your component it gets rendered the first time, once it is mounted componentDidMount gets called, in there you call this.setState again twice. This means your component get's rendered in the best case three times before the user sees your component. If you get multiple promises back this means your rerender your state even more. This can create a lot of load for your component to cope with. If you rerender every component three times or more you end up having some performance issues once your application grows. Try to not call setState in your componentDidUpdate more than once.
In your case your first call to setState is totally unnecessary and just creates load. You still have access to quantities in your promise. Just call setState once at the end of your promise.then() with both elements.
In the example below you are using the index i as a key. This is not a good case practice and react should also log you at least a warning in the console. You need to use a unique identifier which is not the index. If you use the index you can get sideeffects and weird rendering which is difficult to debut. Read more on it here
then(res => {
const allCartItems = res.map((res, i) => {
const data = res.data;
return(
<div key={i} className="cart-item-container">
Another suggestion is to replace all var with const or let, as var exposes your variable to the global scope. If you don't understand what that means read this.
Last but not least have a look at object deconstruction. It can help you to clean up your code and make it more resistant to unwanted sideffects.
I am unable to get props inside constructor that I have implemented using redux concept.
Code for container component
class UpdateItem extends Component{
constructor(props) {
super(props);
console.log(this.props.item.itemTitle) // output: undefined
this.state = {
itemTitle: this.props.item.itemTitle,
errors: {}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
//If the input fields were directly within this
//this component, we could use this.refs.[FIELD].value
//Instead, we want to save the data for when the form is submitted
let state = {};
state[e.target.name] = e.target.value.trim();
this.setState(state);
}
handleSubmit(e) {
//we don't want the form to submit, so we pritem the default behavior
e.preventDefault();
let errors = {};
errors = this._validate();
if(Object.keys(errors).length != 0) {
this.setState({
errors: errors
});
return;
}
let itemData = new FormData();
itemData.append('itemTitle',this.state.itemTitle)
this.props.onSubmit(itemData);
}
componentDidMount(){
this.props.getItemByID();
}
componentWillReceiveProps(nextProps){
if (this.props.item.itemID != nextProps.item.itemID){
//Necessary to populate form when existing item is loaded directly.
this.props.getItemByID();
}
}
render(){
let {item} = this.props;
return(
<UpdateItemForm
itemTitle={this.state.itemTitle}
errors={this.state.errors}
/>
);
}
}
UpdateItem.propTypes = {
item: PropTypes.array.isRequired
};
function mapStateToProps(state, ownProps){
let item = {
itemTitle: ''
};
return {
item: state.itemReducer
};
}
function mapDispatchToProps (dispatch, ownProps) {
return {
getItemByID:()=>dispatch(loadItemByID(ownProps.params.id)),
onSubmit: (values) => dispatch(updateItem(values))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(UpdateItem);
Inside render() method am able to get the props i.e. item from the redux but not inside constructor.
And code for the actions to see if the redux implementation correct or not,
export function loadItemByID(ID){
return function(dispatch){
return itemAPI.getItemByID(ID).then(item => {
dispatch(loadItemByIDSuccess(item));
}).catch(error => {
throw(error);
});
};
}
export function loadItemByIDSuccess(item){
return {type: types.LOAD_ITEM_BY_ID_SUCCESS, item}
}
Finally my reducer looks as follows,
export default function itemReducer(state = initialState.item, action) {
switch (action.type) {
case types.LOAD_ITEM_BY_ID_SUCCESS:
return Object.assign([], state = action.item, {
item: action.item
});
default:
return state;
}
}
I have googled to get answers with no luck, I don't know where i made a mistake. If some one point out for me it would be a great help. Thanks in advance.
The reason you can't access the props in the constructor is that it is only called once, before the component is first mounted.
The action to load the item is called in the componentWillMount function, which occurs after the constructor is called.
It appears like you are trying to set a default value in the mapStateToProps function but aren't using it at all
function mapStateToProps(state, ownProps){
// this is never used
let item = {
itemTitle: ''
};
return {
item: state.itemReducer
};
}
The next part I notice is that your are taking the state from redux and trying to inject it into the component's local state
this.state = {
itemTitle: this.props.item.itemTitle,
errors: {}
};
Mixing redux state and component state is very rarely a good idea and should try to be avoided. It can lead to inconsistency and and hard to find bugs.
In this case, I don't see any reason you can't replace all the uses of this.state.itemTitle with this.props.items.itemTitle and remove it completely from the component state.
Observations
There are some peculiar things about your code that make it very difficult for me to infer the intention behind the code.
Firstly the reducer
export default function itemReducer(state = initialState.item, action) {
switch (action.type) {
case types.LOAD_ITEM_BY_ID_SUCCESS:
return Object.assign([], state = action.item, {
item: action.item
});
default:
return state;
}
}
You haven't shown the initialState object, but generally it represents the whole initial state for the reducer, so using initialState.item stands out to me. You may be reusing a shared initial state object for all of the reducers so I'm not too concerned about this.
What is very confusing the Object.assign call. I'm not sure it the intention is to output an object replacing item in the state, or if it is to append action.item to an array, or to have an array with a single item as the resulting state. The state = action.item part is also particularly puzzling as to it's intention in the operation.
This is further confused by the PropTypes for UpdateItem which requires item to be an array
UpdateItem.propTypes = {
item: PropTypes.array.isRequired
};
But the usage in the component treats it like and object
this.state = {
// expected some kind of array lookup here |
// V---------------
itemTitle: this.props.item.itemTitle,
errors: {}
};
Update from comments
Here is a example of what I was talking about in the comments. It's a simplified version of your code (I don't have all your components. I've also modified a few things to match my personal style, but hopefully you can still see what's going on.