this.props is not a function in the componentDidUpdate() - javascript

I'm facing a weird problem, there's probably a simple thing I didn't noticed but I'm not able to pass through this bug. Here's my case:
In the child, a counter is updated onClick. What I'm trying to do is fire a props function to togle the state of the parent.
In the child:
componentDidUpdate(){
if(this.state.counter === 3){
return this.props.isFullBackgroundHomePage();
}else{
return;
}
}
In the parent:
renderHomePage = () => {
if(this.state.isHomePage
&& this.state.home_page !== null){
return (
<HomePage
isFullBackgroundHomePage={this.isFullBackgroundHomePage}
triggerSeeAllSection={this.triggerSeeAllSection}
{...this.state}
/>
)
}else{
return null;
}
}
isFullBackgroundHomePage = () => {
this.setState({
isFullBackgroundHomePage: !this.state.isFullBackgroundHomePage
})
}
Now, this is what appears in the browser when the counter is at 3:
TypeError: this.props.isFullBackgroundHomePage is not a function
Link to the browser log: https://ibb.co/vcMW3wL

In
<HomePage
isFullBackgroundHomePage={this.isFullBackgroundHomePage}
triggerSeeAllSection={this.triggerSeeAllSection}
{...this.state}
/>
you are overwriting prop isFullBackgroundHomePage using {...this.state} thus its not a function anymore rather a true/false boolean value at the consuming component. In your consuming component if you need both, you should rename state or prop value.
Very common issue unintentionally overwriting props using spread, gets the best of developer musing around what the heck is going on but to avoid such issues, consider using separate naming for function and state value to separate concerns.

Related

React: Tell child component to "reinitialize," even when the passed props are the same

I have a MyComponent that renders a Timer component. My current setup is like this:
MyComponent.render:
render () {
return <Timer time={this.state.time} lag={this.lag || 0} />
}
Timer:
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
time: this.props.time,
};
}
startTimer = (duration) => {
if (duration > 0){
this.on = true;
let timer = duration * 1000 + this.props.lag;
var s = setInterval(() => {
this.setState({time: Math.round(timer/1000)});
timer = timer - 500;
if (timer <= 0) {
this.on = false;
clearInterval(s);
}
}, 500);
}
}
componentDidMount = () => {
this.startTimer(this.props.time);
}
render() {
return (
<div className="Timer-container">
<div className="Timer-value">{this.state.time}</div>
</div>
);
}
}
As you can see, when the Timer is initialized, it immediately starts counting down. On subsequent renders of MyComponent, I want to restart the Timer, even if the time prop doesn't change. In other words, I want it to "reinitialize" on every render. How do I achieve this?
First of all, to reset the counter, you need to store something in the state,
either the interval (so you can clear it)
or the current time (so you can set it to the initial value).
As you want to do something if the parent re-rendered (but the props didn't change), basically what you need to check is why your component updated. An answer to that would be "Trace why a React component is re-rendering"
A quick way for your example would be to check if the state has changed (not recommended):
componentDidUpdate(prevProps, prevState, snapshot) {
if( prevState === this.state ){
clearInterval( this.state.interval );
this.startTimer( this.props.time );
}
}
Another quick solution would be (if it is an option for you) to pass a shouldRerender property to the component, and then check for this property inside the component:
// -- inside MyComponent
render () {
return <Timer
time={ this.state.time }
lag={ this.lag || 0 }
shouldRerender={ {/* just an empty object */} } />;
}
// -- inside Timer
componentDidUpdate(prevProps, prevState, snapshot) {
if( prevProps.shouldRerender !== this.props.shouldRerender ){
clearInterval( this.state.interval );
this.startTimer( this.props.time );
}
}
That looks a bit "dirty" to me. A cleaner way would be to pass some state to shouldRerender, which changes on every update (e.g. just an increasing number).
However, I think the approach to check if parent rendered is not the React way. I, personally, do consider if a component renders or not an implementation detail (I don't know if that's correct to say), that is, I don't care when React decides to render, I only care for props and state (basically).
I would recommend to think about what actually is "cause and effect", what is the reason why you want to reset the timer. Probably the re-render of the parent is only the effect of some other cause, which you might be able to use for your time reset, too.
Here some different concepts that might be useful for use cases I can imagine:
not use one Time instance, but destroy and create inside parent when needed, maybe also using a key prop.
use a HOC (like withTimer) or custom hook (like useTimer), injecting a reset() function (plus create a separate TimerView component)
keep the time state in MyComponent, passing time and onChange down to the Timer component (<Timer time={ this.state.time } onChange={ time => { this.setState({ time: time }); } } />), then both MyComponent and Timer can set / reset the time.

Change a prop value inside functional component can cause problems in react component?

If I receive a prop and change it in my functional component, can it create a problem? Or it's ok to change it inside the component?
e.g
const MyComponent = ({ foo }) => {
// is this bad?
foo = someCondition ? bar : foo
return (...)
}
I know that I could create another variable and store the values, but I would like to know if changing the prop it self won't cause any problem because it's a functional component.
No, it shouldn't create any problems. As with regular functions the arguments passed are their own variables in the function scope and don't mutate the original value passed to the function.
function something(value) {
value = 'nothing';
}
var anything = 0;
something(anything);
// Anything should still be 0;
console.log(anything);
But I would suggest to not mutate your variables.
If foo in your example is passed from the parrent, and the parrent keeps it in its state, then you would also need to pass setFoo as a paramater to your component and use that to update it properly.
function Parrent(){
let [foo, setFoo] = useState('foo');
return <Child foo={foo} setFoo={setFoo}/>
}
As for changing the props directly, you can if they are arrays or objects.
Props in the React are just read-only variables. You should change the props values by the parent component
I avoid changing the prop.
But I created a simple example and changing the prop in the children do not affected the value in the parent component.
https://codesandbox.io/s/objective-cdn-cq55d
I tested it with several render. Thats why I added an input. Typing in it makes the component rerender.
const MyComponent = ({ foo }) => {
// Not valid
foo = someCondition ? bar : foo
return (...)
}
There are two kinds of data in React,
a)Props(immutable data)
b)State(mutable data)
you are not supposed to change the immutable data(there are some ways to do it, but not recommended). what you should do is, (you can't assign a callback and change from here, i'll explain later why)
if you want to just use just the value inside this component
const baz = foo === condition ? bar : foo
or render something based on foo meets some condition
return (
<div>
{foo === somecondition ? <A /> : <B />}
</div>
)
Or you want to actually change it,
coming from a global state like redux or mobx,
u should change it from the reducers in case of redux or
#action decorated functions in mobx.
if it's a local state which passed down to the child component,
u can set a call back and assign to a click handler in which the case it is feasible
handleClick = () => {
this.setState(prevState => ({
...prevState,
foo: someCondition ? bar : foo,
}))
}
render () {
const { handleClick } = this
return <ChildComponent {...{ handleClick }} />
}
Like said before u can't change the passed down local state from render of the child(or render of any component[actually u can, but may end up in infinite loops: each time a state change happens, the component will re render, so the loop(pure components excluded eg: shouldComponentUpdate() hook which validates an unrelated condition)])
in such cases what u should do is to make the child component also a stateful component and change the parent props with a callback
class Child extends Component {
//set once
componentWillMount() {
if (condition) callback
}
//whenever there is change
componentWillReceiveProps(nextProps) {
if (condition) callback
}
//use correct lifecycle method which meets your requirement..
}

ReactJS: Passing state to child returns undefined

I am setting the state in a parent component. Then when that state is set (with componentDidMount) I need to pass that to a child.
Currently, since it is returning undefined in the child component. I presume this is because state isn't set at the moment that the non-existent state is being passed. Making it undefined.
Tried putting boolean conditions in for state so it needs to be true before passing. Booleans in the child, in the parent, anywhere i thought it might make a difference and would prevent the component for looking for the prop I'm passing before it exists.
yearArr is being set here with the result of a function being executed as the component is mounted
componentDidMount() {
const yearArrSorted = dataSet && this.playFunction();
this.setState({
locationData: dataSet,
yearArr: yearArrSorted
});
this._layerRendering();
this._animate();
}
here is the func:
playFunction = () => {
//i know this is unreadable, but i wanted to see how simple i could go.
return dataSet.map(i => {
return Object.keys(Object.values(i)[2])
.map(i => (i.startsWith(9) ? 19 + i : 20 + i))
.sort();
})[0];
};
Here's state:
this.state = {
locationData: "",
year: 95,
elevationScale: elevationScale.min,
yearArr: []
};
Hjere is where state is being passed, with my terribly hacky approach to try to get the state only being passed once its set:
<YearSelector
yearOnChange={this.yearOnChange}
year={this.state.year}
dataStateChange={this.dataStateChange}
dataSet={dataSet}
years={this.state.yeaArr.length > 0 && this.state.yeaArr}
/>
I expect to send state to the child component as a prop when it is set so that prop can be used to .map through the prop array and create a <select> DOM element with <option>
Can edit question as needed and code can be supplied.
Sorted the issues.
Tried this initially but it didn't work for whatever reason.
years={this.state.yearArr && this.state.yearArr}
Now it works, the method i tried at first, which is a status quo for react.

Passing a function through three levels of components (i.e. calling a function from a grandchild component)

I want to pass an item id from either a Child component or grandchild component, but cannot figure out how to do so. Other examples I have looked at show using the arrow function to achieve this, but for whatever reason my function is not getting called.
I have the following in Parent.js:
chosenItem(id){
console.log("CHOSEN ITEM SELECTED")
this.setState({
chosen_item: id
})
}
and in the Parent.js render function:
<Child objects={objects} chosenItem={() => this.chosenItem()} />
Then in my Child.js I have:
items = this.props.objects.items.map(item => {
return (
<ClickableTextComponent
key={item.id}
text={item.label}
onClick={item =>this.props.chosenItem(item.id)}
/>
)
})
In my Child.js render function I have:
{items}
I also wasn't sure whether the actual click event should go inside of the Child.js or the ClickableTextComponent. Or does it really matter? Currently I have placed it in the Child.js component as seen above.
What am I doing wrong? How can I modify my code so that the function gets called? I have read a bit about currying to prevent a function from being recreated multiple times. Is that necessary in this case? If so, where and how should I be implementing it. In my Parent or Child components?
Update
I was previously trying to get the onClick to work from Child.js, but as it needs to be attached to a div I have moved it to ClickableTextComponent (the grandchild component).
One issue with ClickableTextComponent is that I want to be able to set the state when the component is clicked so that I can turn the component a different colour. Because of that I am needing to use a function to then call the chosenItem function. So here is what I have in my `ClickableTextComponent.js':
handleClick(){
this.setState({
text_state: "clicked"
})
this.props.chosenItem()
}
In the render I then have:
<div
onClick={this.handleClick.bind(this)}
onMouseOver={this.changeTextState.bind(this, "hover")}
onMouseOut={this.changeTextState.bind(this, "default")}
>{this.props.text}</div>
New Error
Based on the above changes, I am now getting this.props.chosenItem is not a function. However, I cannot figure out why it is giving me this error. I can see the function name when I display this.props to the console. What am I doing wrong?
The answer given by Kevin He holds true. But there is one problem with that solution.
<Child objects={objects} chosenItem={(x) => this.chosenItem(x)} />
When you do such, every time your parent is rerendered. It will create a new instance of the function. And, your child component also rerenders because It sees the props changing.
Best solution is:
<Child objects={objects} chosenItem={this.chosenItem} />
Update:
Now, it seems to make sense.
The problem is again with ClickableTextComponent.
Here is the update ClickableTextComponent which works.
https://codesandbox.io/s/73x6mnr8k0
The main problem:
items = this.props.objects.items.map(item => {
return (
<ClickableTextComponent
key={item.id}
text={item.label}
onClick={item =>this.props.chosenItem(item.id)}
/>
)
})
//
// Here you made a function (item) => this.props.choseItem(item.id)
// That means when you call that function you should call like this
// i.e. passing parameter needed for the function
//
handleClick(){
this.setState({
text_state: "clicked"
})
this.props.chosenItem(item)
}
//
// But do you do not have the item in the children
// Parent should be changed as below
//
items = this.props.objects.items.map(item => {
return (
<ClickableTextComponent
key={item.id}
text={item.label}
onClick={() =>this.props.chosenItem(item.id)}
/>
)
})
//
// Now you made a fuction () => this.props.chosenItem(item.id)
// The main difference being you are not taking a item as parameter
// item will be taken from outer scope that means, item from map
//
//
// Another solution can be
//
items = this.props.objects.items.map(item => {
return (
<ClickableTextComponent
key={item.id}
id={item.id}
text={item.label}
onClick={this.props.chosenItem}
/>
)
})
// And in ClickableTextComponent
handleClick(){
this.setState({
text_state: "clicked"
})
this.props.chosenItem(this.props.id)
}
You can do this:
<Child objects={objects} chosenItem={(x) => this.chosenItem(x)} />
Note that chosenItem is a function, then whenever it's called with item.id, it will take call the function this.chosenItem at the parent element.

React parent component check if child will render

I have a child component that depending on some of its props will end up rendering something or not. The render function of the children looks something like this:
render() {
if (props.a == 'foo' && props.b == 'bar') {
return (<p> Hey There </p>);
} else if {props.a == 'z') {
return (<p> Hey There </p>);
} // more conditions
} else {
return null
}
}
In the parent component I am rendering several child components, and I need to know how many of them will render, because depending on that number I will do something or not. I don't want to repeat the conditional logic from the child to the parent, but I don't know how from a parent I can find out if the children will render or not.
What you're trying to do is a bit of an anti-pattern in React.
If I understand your question, the children render output would influence their parent's render ouptput, which is likely to get you stuck in a render loop.
I suggest you keep the children components as simple as possible and hoist the conditional logic to the parent which will allow you to count how many children you'll be rendering in place.
Please let me know if I got your question wrong.
Although I agree with perpetualjourney's answer, I thought I could give you a possibility of counting the children.
The easiest would be to save the rendering of the children first to a variable and then to render that result.
var kids = this.props.items.map( (k, i) => <Kid condition={k} key={i} /> );
var totalKids = kids.reduce( (k, i) => k + (i.type( i.props ) ? 1 : 0), 0);
Now, this will render your children twice, so it is not the best when you already have a performance heavy method
const Kid = ({ condition }) => condition % 3 === 0 ? <h1>{ condition }</h1> : null;
class ConditionalKids extends React.Component {
render() {
var kids = this.props.items.map( (k, i) => <Kid condition={k} key={i} /> );
var totalKids = kids.reduce( (k, i) => k + (i.type( i.props ) ? 1 : 0), 0);
return <div>
<p>Rendered in total { totalKids }</p>
{ kids }
</div>;
}
}
const items = [...new Array(5)].map( i => parseInt( Math.random() * 10 ) );
console.log( items );
const target = document.querySelector('#container');
ReactDOM.render( <ConditionalKids items={items} />, target );
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<div id="container"></div>
One way of handling this problem is to use state.
In this CodeSandbox example, I use the if/else logic to update the component's state. This is done inside the componentWillReceiveProps lifecycle method.
Your render method is simplified to a switch statement.
If you want to call an imperative API based on the if/else logic you have, you can do that inside your componentDidUpdate lifecycle method. This is also simplified to a switch statement.
That's a very pertinent question that affects applications of any complexity. Unfortunately there's no clear way to model this using React today. I've done a ton of React and would recommend what #perpetualjourney said: hoist the logic to a common parent. Sometimes you'll need to move it way up, but do it instead of messing around with React.Children.map or React.Context since they'd create dependency between the components and would make it harder for you to move them around if needed.
const hasA = computeHasA({ ... });
const hasB = computeHasB({ ... });
const hasAny = hasA || hasB;
if (hasAny) {
return (
<Tabs>
{hasA && <Tab title="A">Some content</Tab>}
{hasB && <Tab title="B">Another content</Tab>}
</Tabs>
);
}
When you really can't move to logic upwards, for example, if the children do network requests, which is probably an edge-case, I'd recommend passing a prop used to report whether they have content or not and storing it into the parent's state:
const [numberOfRenderedChildren, setNumberOfRenderedChildren] = useState(0);
const incrementNumberOfRenderedChildren = () => {
// Use a callback to prevent race conditions
setNumberOfRenderedChildren(prevValue => prevValue + 1);
};
return (
<MyContainer isVisible={numberOfRenderedChildren > 0}>
{items.map(item => (
<ComplexChild
key={item.id}
// Avoid spreading props like ...item. It breaks semantics
item={item}
reportHasContent={incrementNumberOfRenderedChildren}
/>
)}
</MyContainer>
);
It could get more complex if you need loaders and error messages, but then I'd fire all the requests in the parent. Try to keep it simple, easy to read, even if it means more verbosity. You won't regret when you need to come back to those files a few weeks later.

Categories

Resources