ReactJs - unable to update state with setState - javascript

I'm having trouble updating my state using setState.
I have a parent component where I define my state, as well as a handleClick function which should update the state.
export default class Main extends PureComponent {
state = {
selectedName: '',
selectedTasks: [],
data: [
{
name: 'John',
tasks: ['vacuum', 'cut grass']
},
{
name: 'Jack',
tasks: ['cook breakfast', 'clean patio']
}
]
handleClick = e => {
console.log('Name: ', e.name);
console.log('Tasks', this.state.data.find(el => el.name === e.name).tasks)
const { selectedName, selectedTasks } = this.state;
this.setState({
selectedName: e.name
selectedTasks: this.state.data.find(el => el.name === e.name).tasks
});
console.log('stateAfter', this.state)
};
render() {
const { data } = this.state;
return (
<div>
<Child
data={data}
handleClick={this.handleClick.bind(this)}
/>
</div>
)
}
}
I have a child component that takes the handleClick as props.
export default class Child extends PureComponent {
constructor(props) {
super(props);
}
render() {
console.log('data', this.props.data);
return (
<ResponsiveContainer width="100%" height={500}>
<PieChart height={250}>
<Pie
data={this.props.data}
onClick={this.props.handleClick}
/>
</PieChart>
</ResponsiveContainer>
);
}
}
When I execute the handleClick function the first time, nothing gets updated. But when I execute it the second time, the state does get updated. What am I missing to make state get updated the first time?

this.setState is asynchronous. If you wish to console.log your state after setState then you'll have to do this via the callback.
this.setState({
selectedName: e.name
selectedTasks: this.state.data.find(el => el.name === e.name).tasks
},
() => {
console.log('stateAfter', this.state)
});

Related

Child React Component Will Not show on state change

I am having an issue getting a third component to show up using REACT.
I am trying to change the state of workflow, and show Component Three, but I am not sure what I am doing wrong.
After clicking on the continue button, the state changes.
Workflow is changed to WELCOME_MSG, the switch below works.
But I can't seem to return this Component "ComponentThree"
case 'WELCOME_MSG':
return ();
class ComponentOne extends React.Component {
constructor(props) {
super(props)
this.state = {
workflow: 'GET_NAME'
}
this.setWorkflow = this.setWorkflow.bind(this);
}
setWorkflow() {
switch(this.state.workflow){
case 'GET_NAME':
return (<ComponentTwo/>);
case 'WELCOME_MSG':
return (<ComponentThree name={this.state.name} />);
}
}
render() {
console.log('ComponentOne: ',this.state.workflow );
return this.setWorkflow();
/*
switch(this.state.workflow){
case 'GET_NAME':
return (<ComponentTwo/>);
case 'WELCOME_MSG':
return (<ComponentThree name={this.state.name} />);
} */
}
}
// showThree()
class ComponentTwo extends React.Component {
constructor(props){
super(props)
this.state = {
workflow: 'GET_NAME',
name: 'Chris'
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.setWorkflow = this.setWorkflow.bind(this);
}
handleChange(e) {
//this.setState({name: event.target.name});
e.persist();
console.log('handleChange event.target.name:', e.target);
this.setState((prevState, props) => {
return {
name: e.target.value,
workflow: 'WELCOME_MSG',
}
})
/*
this.setState(state => ({
//name: this.state.name,
name: e.target.value,
//name: "sadfasdf",
})); */
}
setWorkflow() {
console.log("setWorkflow", this.state.workflow);
switch(this.state.workflow){
case 'GET_NAME':
return (<ComponentTwo/>);
case 'WELCOME_MSG':
return (<ComponentThree />);
}
// name={this.state.name}
}
handleClick(e) {
//console.log(this.state);
//e.preventDefault();
console.log('BEFORE handleClick this STATE ON CLICK :', this.state.workflow);
console.log('this.state.name:', this.state.name);
this.setState((prevState, props) => {
return {
workflow: 'WELCOME_MSG',
name: this.state.name,
}
})
return this.setWorkflow();
//this.setWorkflow = this.setWorkflow.bind(this);
/* this.setState(state => ({
//name: this.state.name,
name: this.state.name,
workflow: 'WELCOME_MSG'
})); */
console.log('ON CLICK AFTER SET STATE:', this.state);
//return (<ComponentThree name={this.state.name} />);
// e.preventDefault();
}
render() {
//console.log('this is:', this);
// onChange={this.handleChange}
return (
<div>
<h1>Enter your name</h1>
<div className="grid20 md-grid100">
<input type="text" name="fname" value={this.state.name} onChange={this.handleChange} />
</div>
<div className="grid80 md-grid100">
<button onClick={this.handleClick} >Continue</button>
</div>
</div>
)
}
}
class ComponentThree extends React.Component {
render() {
console.log('ComponentThree this is:', this);
return (
<div className="test">
<h1>Hello {this.state.name}</h1>
<h2>sfasfdadf</h2>
</div>
)
}
}
ReactDOM.render(<ComponentOne />, document.querySelector("#app"))
JS Fiddle Below
https://jsfiddle.net/ameshkin/cvg2rjzo/32/
The state variable to show ComponentTwo or Three is in ComponentOne, but you are updating the state in ComponentTwo, and not updating the state in ComponentOne. To update the state in ComponentOne, you need to pass a function from One to Two in the props.
Edit:
Here's a working fiddle https://jsfiddle.net/j5gxovdm/
Basically having the state in one place, instead of spread across all components (keeping a single source of truth)
return (
this.state.workflow == 'GET_NAME'
? <ComponentTwo
setWorkflow={this.setWorkflow.bind(this)}
onChange={(e) => this.setState({name: e.target.value})}
name={this.state.name}
/>
: <ComponentThree name={this.state.name} />
)
ComponentTwo has its own state and that state is updated upon clicking the button, not the ComponentOne state. As ComponentOne is a container component to switch ComponentTwo and ComponentThree, you have to pass the ComponentOne state as props for ComponentTwo and ComponentThree.
JS Fiddle Below
https://jsfiddle.net/richard929/g0b4d3ur/1/

How pass data from the child component (the child has its own state) to the parent?

Expected effect: click button -> call function setEditing() -> call function item() inside setEditing() -> this.state.isEditing changes to true -> in parent this.state.isEdit changes to true. When I call the item () function, the value of isEditing does not change
App
class App extends React.Component {
constructor() {
super();
this.state = {
isEdit = false;
};
}
handleSomething = (value) => {
this.setState(prevState => {
return {
isEdit: value
};
});
}
render() {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
handleSomething={this.handleSomething}
/>
)
}
</ul>
</div>
);
}
}
Todo
class Todo extends Component {
state = {
isEditing: false
}
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
})
this.item();
}
item = () => {
const { isEditing} = this.state;
this.props.handleSomething(isEditing);
}
render() {
return (
<button onClick={() => this.setEditing()}>Edit</button>
)
}
}
You'll need to call this.item after the state was changed, something like
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
}, this.item)
}
Also, if you want to derive a new state form the old one, you'll have to use something like this:
setEditing = () => {
this.setState(prevState => ({
isEditing: !prevState.isEditing
}), this.item)
}
Try basing your state change on the previous state, and call parent function in a callback :
setEditing = () => {
this.setState(prevState => ({
isEditing: !prevState.isEditing
}), this.item)
}
Because as written in the React doc :
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
(https://reactjs.org/docs/react-component.html#setstate)
class Todo extends React.Component {
state = {
isEditing: false
}
setEditing = () => {
this.setState({
isEditing: !this.state.isEditing
},this.item())
}
item = () => {
const { isEditing} = this.state;
this.props.handleSomething(isEditing);
}
render() {
return (
<button onClick={() => this.setEditing()}>
Edit
</button>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
isEdit : false,
todos : [
"test 1",
"test 2"
]
};
}
handleSomething = (value) => {
this.setState(prevState => {
return {
isEdit: value
};
});
}
render() {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
handleSomething={this.handleSomething}
/>
)
}
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<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="app"></div>

Refresh a specific component 's data/ state in React

I have a List of products-ID and a button. When I press the button, I want to refresh the data in the ListComponent. I have no idea how can I do this in React. Can someone help me?
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state); //function that fetches-takes all products
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
};
};
Your refresh function needs to call an action that fetches the data, and updates the Redux store accordingly. And because you've mapped part of your Redux state to this component's props, it will re-render when that data is fetched and saved via the reducer.
Therefore, you don't need to set local state at all in this component. Provided you have an action called fetchProductData:
class ProductList extends React.Component {
constructor (props) {
super(props)
this.refresh = this.refresh.bind(this)
}
// if you don't already have the data in your store, you can fetch it here to kick things off
componentDidMount () {
this.props.fetchProductData()
}
refresh () {
this.props.fetchProductData()
}
render () {
const { products } = this.state
return (
<div>
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
const products = selectAllProducts(state)
return {
productData: products.map(products => ({
productId: product.get("productId")
}))
}
}
export default connect(mapStateToProps, { fetchProductData })(MyComponent)
Again, this assumes that fetchProductData dispatches an action that will update the redux state where products are stored. Passing the action to connect like this will make it available as a prop within the component.
It looks like you've placed your refresh() inside the constructor, try:
constructor(props) {
super(props);
this.state = {
products: this.props.productData //where productData an array of all products-ID
};
this.refresh = this.refresh.bind(this);
}
refresh() {
this.setState({ products: null });
this.forceUpdate();
}
render() {
const { products } = this.state;
<Button onClick={this.refresh} />
<ListComponent
data={products.map(entry => ({
text: entry.productId
}))}
/>
);
}
I made a minimal component that does what you want it to do. Instead of binding in the constructor i use a fat arrow function for refresh.
import { Component } from "react";
const ListItem = props => props.item.text;
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [{ id: 0, text: "zero" }, { id: 1, text: "one" }]
};
}
refresh = () => {
this.setState({ items: [] });
};
render() {
const { items } = this.state;
return (
<div>
{items.map(i => (
<div key={i.id}>
<ListItem item={i} />
</div>
))}
<button onClick={this.refresh}>refresh</button>
</div>
);
}
}
export default List;
You don't need to forceUpdate(), the component will re-render by default when its props are changed.
For an explanation of the fat arrow and what it does to this, check out https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4.

Setting State with Objects from Firebase

I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;

React AddItem to list in another component

I would like to use AddItem to add items to a list in another component. However, I keep getting undefined.
How do I correctly add an item to a list?
I've put it inside a CodeSandbox too: https://codesandbox.io/s/Mjjrm3zrO
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [x.movies],
};
}
render() {
return (
<div>
<CreateNew addItem={item => this.setState({ movie: [item].concat(this.state.movie) })} />
{x.movies.map(movie => (
<Result key={movie.id} result={movie} addItem={item => this.setState({ genres: [item].concat(this.state.genres) })} />
))}
</div>
);
}
}
class CreateNew extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
genres: '',
};
}
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state.value)
console.log(this.props.item);
};
onChange = e => {
this.setState({ value: {'name': e.target.value}, genres: [{ name: 'Test', type: 1 }, { name: 'Foo', type: 10 }] });
console.log(this.state.value);
};
render() {
const { value, genres } = this.props;
return (
<form onSubmit={this.handleSubmit1}>
Add a new movie
<input onChange={this.onChange} type="text" />
<button type="submit">Save</button>
</form>
);
}
}
class Result extends React.Component {
render() {
const { result } = this.props;
return (
<div>
<li>
{result.name} {' '}
({result.genres.map(x => x.name).join(', ')}){' '}
</li>
</div>
);
}
}
Changes:
1. Instead of sending only name from child component send the whole state variable that will contain name and genres.
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state)
};
2. You are storing the initial value movies from json to state variable so use that state variable to create the ui, because you are updating the state variable once you adding any new item, so if you use initial json to create ui then new item will not reflect in ui.
{this.state.movies.map(movie => (
<Result key={movie.id} result={movie} />
))}
3. Update the state variable movies like this:
<CreateNew addItem={item => this.setState({ movies: [{name: item.value.name, genres: item.genres}].concat(this.state.movies) })} />
Check the working solution: https://codesandbox.io/s/3nD0RgRp

Categories

Resources