ReactJS - setState fails to update property value - javascript

I have a property name in a React component. Using setState I am trying to modify this property. But assigning a new value to this property inside setState has no effect. Please find below my sample code.
export const Test = ({
name,
changeState = function (newName) {
this.setState({name: newName}, () => console.log('Name after setState # ' + name)); //prints old value
console.log(name); // Doesn't reflect changes. Prints old name
}
}) =>
(
<div>Some data</div>
)
User action will trigger a call to changeState(newName). I am calling setState inside changeState function.
After calling setState if I print the name variable to console it still holds old value.
How can I make setState assign a new value to name?
I am aware that setState is asynchronous and update to property may reflect after a delay. But in case of example code above name variable is never updated even after a delay.
I have implemented componentWillReceiveProps(nextProps) method and it gets called but it always receives old name. So I don't think the issue is related to setState being asynchronous.
I have created an example app to demonstrate the issue I am facing. The example app runs fine locally and I can reproduce the issue. But I am not being able to get the same example app code working in codepen. it's giving me some 'unexpected token' errors. But I hope looking at the code will help understand my issue better. The code is based on existing application structure.
Codepen example here.
In the code I have a Parent and two children Child1 and Child2. In Parent I have a function changeState defined which I am passing to Child1 as property. And to Child2 I am passing a 'name' property from Parent.
Clicking on 'Change Name' button in Child1 triggers a call to changeState function of Parent. Initial value of name in Parent is 'Robert'. changeState is invoked from Child1 with new name value of 'Tom'. But in changeState function assigning new value to 'name' using setState has no effect. I am calling a function to print the 'name' after setState has completed but it prints old name value and NOT the new one assigned in setState.

You are using stateless component, there's for, there is no state so the setState function doesn't affect anything.
there are 2 options to deal with it:
the easiest one, but most likely not the best option, just change your component to regular component:
export class Test1 extends React.componnent {
...
(the rest of your component)
}
the second option (usually a better one in my option) is instead of changing the state for the component, get an event from the parent component, and call the event where you wanted to call the setState, the event would contain an updating the value as requested, and the child component would receive it as prop, and your component wouldn't have to change to Container (a component with state)
Good luck!

Related

How do I tell another component that the setState of useState changes are done so when they get the value it will be the updated value from an event?

Similar to:
How to use `setState` callback on react hooks but I am trying to do this with functional component specifically and I am not trying to replicate the exact same scenario.
React hooks: accessing up-to-date state from within a callback but I am not passing the state from the callback
So say I have a notify() method that I want to be fired AFTER the states have changed, not during state change. Also after I click.
something like
const handleClick = useCallback(()=>{
const [state,setState] = useState("bar");
...
setState("foo", ()=> {
... at this point `state` should be "foo" ...;
notify("yo we're set. but I am not passing the current state to you");
})
});
...
a component that is child of the context
const { state } = useSomeContext();
subscribe(()=>{
console.log("I want ", state, " to be the updated value, but I get the existing value);
});
useStateRef lets the callback know but the ones listening when they query the value that is in the state may not get the updated value.
useStateCallback does not solve the problem for me either because it's the callback that has the value.
The workaround I sort of ended up with (still testing it) is to utilize a useRef to the value somewhat like useStateRef and do stateRef.current instead of just state
When you change the state via the setter, it will cause the current component to re-render. If the component you want to 'notify' is within the current component and has a dependency on the state value that changed e.g.:
return (
<>
<SomeOtherComponent something={state}/>
</>
)
... then that component will re-render as well.
If the component you want to notify isn't in the render path, you can make the state a dependency of a useEffect so your useEffect will run whenever the state change (and do your notification there). You can have multiple useEffects with with dependencies on different state. Example:
useEffect(()=> {
console.log('hello, state is',state)
}, [state])
And you can look into context and other mechanisms to communicate state. You should read this article. I think it's very inline with what you'd like to do.
Certainly don't do anything in your useState setter other than the minimal needed to return the new state.

React: Passing props to Component's inner function fails

I'm testing if it is possible to have a Button component which changes a 'counter' state, but the nature of the change depends on another state 'type'. Doing console log of the prop 'type' inside the Button component it appears to get correctly the content of it, but when passing it to a inner funtion 'handler', it turns to 'undefined'. Any idea of why this happens?
App.jsx
Button.jsx
Input.jsx
Should be
const handler = (type, setCounter) =>`

Don't understand how JavaScript assign operator interacts with a function

My problem is that I don't understand how assign operator is interpreted and interacts with a function. Let me explain
I was changing the state of my component, and I was doing something wrong that I couldn't see until I realize it, of course.
My component:
class UpdateLifeCycle extends Component {
state = { src: urls[this.props.election] };
componentWillReceiveProps(nextProps) {
this.setState = { src: urls[nextProps.election] };
}
render() {
return (
<div>
<p>Selected {this.props.election}</p>
<img //
alt={this.props.election}
src={this.state.src}
witdh="250"
/>
<p>{urls[this.props.election]}</p>
<p>{this.state.src}</p>
</div>
);
}
}
What I was doing, and is wrong is this piece
this.setState = { src: urls[nextProps.election] };
and what I should be doing is passing it as a parameter instead of assigning it.
this.setState({ src: urls[nextProps.election] });
Why does the JS interpreter allow you to assign and object to a function, and where is that object being assigned?
TL;DR
setState is a variable which currently holds a function. There is nothing stopping you from changing its value and add an object there, because it is a variable. JavaScript was influenced by other functional programming languages and it is okay to assign a function to a variable.
Explanation
state is a special object in a React component, which you would access as this.state. setState is a special function in a React component, which allows you to modify the state. Actions are dispatched when the state is updated, however you should not directly modify the state of a React component, and should always use setState.
setState is another variable in a JavaScript class (Your React Component is a class-based component), which by default holds the function to modify the state variable.
You can override a variable in JavaScript and when you assign an object (the new state you wanted) to setState, you are changing the value of setState which can no longer now update the state variable.
You would find React's docs helpful.

Calling setState without triggering re-render

I am storing a UI state in the React component's state, say this.state.receivedElements which is an array. I want re-renders whenever an element is pushed to receivedElements. My question is, can I not trigger rendering when the array becomes empty ?
Or in general, can I call setState() just one time without re-render while re-rendering all other times ? ( are there any options, work-arounds ? )
I've read through this thread: https://github.com/facebook/react/issues/8598 but didn't find anything.
I want re-renders whenever an element is pushed to receivedElements.
Note that you won't get a re-render if you use:
this.state.receivedElements.push(newElement); // WRONG
That violates the restriction that you must not directly modify state. You'd need:
this.setState(function(state) {
return {receivedElements: state.receivedElements.concat([newElement])};
});
(It needs to be the callback version because it relies on the current state to set the new state.)
My question is, can I not trigger rendering when the array becomes empty ?
Yes — by not calling setState in that case.
It sounds as though receivedElements shouldn't be part of your state, but instead information you manage separately and reflect in state as appropriate. For instance, you might have receivedElements on the component itself, and displayedElements on state. Then:
this.receivedElements.push(newElement);
this.setState({displayedElements: this.receivedElements.slice()});
...and
// (...some operation that removes from `receivedElements`...), then:
if (this.receivedElements.length) {
this.setState({displayedElements: this.receivedElements.slice()});
}
Note how we don't call setState if this.receivedElements is empty.
What about useRef?
Documentation says:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
So if you change ref value inside useEffect it won’t rerender component.
const someValue = useRef(0)
useEffect(() => {
someValue.current++
},[])

React - updating state during render produces errors

I'm new to React and am trying to update the state of a parent component from the child everytime an onChange action happens. The onchange action comes from an input box that when letters are typed it updates the state of searchInputVal with the value of what has been typed. I have a parent <App/> component with the following properties and states here:
updateSampleFilteredState(filteredSamples) {
this.setState({
samples: filteredSamples
});
},
getInitialState () {
return {
samples:allSamples,
searchInputVal:""
}}
I pass the properties and states down to a child component here:
updateNewSampleState(filteredSamples){
return (
this.props.updateSampleFilteredState(filteredSamples)
)
}
render() {
const filteredSamples = this.props.samples.filter(sample => {
return sample.sampleFamily.toLowerCase().indexOf(this.props.searchInputVal.toLowerCase()) !== -1;
});
this.updateNewSampleState(filteredSamples);
return <div className="samples-container-inner-styling">
{
filteredSamples.map((sample) => {
return (...
Before I added the line this.updateNewSampleState(filteredSamples); the child component would render out the filtering just fine but obviously not update the state of sample with the new filtered state. When I the line this.updateNewSampleState(filteredSamples); to execute the function in the component to set the new state I get a list of re-occuring errors that eventually make my app crash. The errors say something about an anti pattern. I'm not sure how else to update the state?
You should't be updating the state from the render function, and you are facing the reason why that's a bad way to do things. Every time you call the setState the component re-renders, so if you call it inside the render function it will be called again and so on... You should ask yourself why are you calling that function there. I guess you could just do it in the onChange function you are using for the input.
As already mentioned by #César, setting the state in the renderer doesn't make sense, since setting the state triggers a rerender of the component, so you basically get something like an infinite render loop.
Given that you are computing filteredSamples only from the props, you could compute that state in the constructor:
The constructor is the right place to initialize state.
However, note the following when deriving state from props in the constructor:
It's okay to initialize state based on props if you know what you're doing. [...]
Beware of this pattern, as it effectively "forks" the props and can lead to bugs. Instead of syncing props to state, you often want to lift the state up.
If you "fork" props by using them for state, you might also want to implement componentWillReceiveProps(nextProps) to keep the state up-to-date with them. But lifting state up is often easier and less bug-prone.

Categories

Resources