Unable to use setState in react component - javascript

I have the following code that is not letting me set the state ever after getting mounted.
Here is the code
import React, { Component } from 'react';
import Messages from '../locale/en/Messages';
import '../styles/base.css';
class AlertService extends Component {
state = {
message: '',
classType: 'alert-info',
isMessageSet: false
}
Messages = new Messages();
componentDidMount = () => {
console.log('This has mounted'); // This is working
}
componentWillUnmount = () => {
console.log('Is this getting unounted ?'); // This is working, the component is not getting unmounted
}
setAlert = (key, type, isMessage, readMore) => {
let message = isMessage ? key : this.Messages[key];
let classType = 'alert-info';
if (type === 0) {
classType = 'alert-danger';
} else if (type === 1) {
classType = 'alert-success';
}
this.openMessage(message,classType);
}
openMessage = (message,classType) =>{
this.setState({
message: message,
classType: classType,
isMessageSet: true
});
}
closeMessage = () => {
this.setState({
message: '',
classType: 'info',
isMessageSet: false
});
}
render() {
let classes = this.state.classType + ' ' + 'alertBox';
return (this.state.isMessageSet ?
<div className={classes}>
<div className="col-md-11"> {this.state.message} </div>
<div className="col-md-1 closeAlert" onClick={this.closeMessage}> x </div>
</div>
: null
)
}
}
export default AlertService;
I am geting the following error when trying to call the function setAlert from outside this component.
However if I set the isMessageSet property to true then on clicking the X and calling the closeAlert method, it works fine.
componentDidMount indicates that the component is getting mounted and componentWillUnmount is never getting executed , I am not sure what is wrong here
Error Message
Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the AlertService component.

setState should not be called from outside the component. If you want to change the state from outside, use props.
And as the error message says, the component is not mounted. You could mount it by adding <AlertService /> to the Layout.

As you have mentioned in one of the comments that you are trying to call the setAlert function after instantiating the AlertService class, I suggest you to please take a look as to how you are doing that. The right way is:
this.AlertService = React.render(React.createElement(AlertService), mountnode) //mounts the component
this.AlertService.setAlert() // now you can call the function
Depending upon your usecase you can do like above.
The thing with react is that the child component's methods cannot be called by the parent component. Since those are meant to be private to the child and should therefore be handled by itself. As a hack though we can make use of refs to call the child component's methods but that is not a recommended usecase for refs. Thid can result in bugs in you application.
The best way to achieve the prupose as #vijayst suggested is to change state of your parent component on an alert(whenever message is received) and pass it as a prop to the child. Now update the state for the child under componentWillReceiveProps().

If I understand you correctly you said you tried calling setAlert from another component, it doesn't work but when you called closeMessage it works as expected, but then I noticed you called closeMessage in this same component which would work as expected, if you want to call a function that belongs to this component in another component then you need to import the component into this component then pass the function to it so you would be able to call the function in the component. For example:
import AnotherComponent from '../AnotherComponenet'
<AnotherComponent setAlert={this.setAlert} />

Related

React complains that hook is not used inside of a body function when my component is passed into another component to render

I have a component that uses another component as a renderer. However, React doesn't allow me to use hooks in the renderer component as it throws the following error:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
Here's an example of what I have in my display component which is an error boundary:
class DisplayComponent extends Component {
// ...
render() {
const { rendererComponent } = this.props;
return rendererComponent({ enabled: true, name: 'hello-world' })
}
}
As you can see, the rendererComponent prop in the DisplayComponent is called as a function though it's a component and I pass in some props into it as I call it this way.
Now, in my other file where I use the DisplayComponent, it looks something like this:
const RendererComponent = (props) => {
const { enabled, name } = props;
const [testState, setTestState] = useState(); // Error: Invalid hook call
useEffect(() => {
// Error: Invalid hook call
}, [testState]);
return (
<div>{name} - {enabled}</div>
)
}
const App = () => {
return (
<DisplayComponent rendererComponent={RendererComponent} />
)
}
Here's the key thing. For some reason, I can't use hooks such as useEffect, useState, etc in the RendererComponent. If I use it, React will throw the error I mentioned above that the hook is invalid. But I would like to have some logic which requires hooks in the RendererComponent.
How can I use hooks to have states and effects in a component that is called the way I did?
You are making a function call instead of mounting the component, those are two different calls and syntax, change it to:
class DisplayComponent extends Component {
render() {
// Custom component must be Capitalized
const { rendererComponent: RenderedComponent } = this.props;
return <RenderedComponent enabled name="hello-world" />;
}
}
// Notice that the JSX is transpiled to
React.createElement(RenderedComponent, {
enabled: true,
name: "hello-world"
});
In React, you can only mount a component by calling its API with React.createElement (JSX is its sugar syntax), while a simple function call is purely JS which does not trigger React API i.e won't register the component to the component tree. Therefore inner implementation like hooks has no meaning, you will lose state on every render (the Reconciliation won't be triggered).

Passing a function to edit state from class component to function based component through props not working

I have a react frontend and most of my components are Class Based components, but it just so happens that one of the Components inside these class components has to be a functional component, so I currently have a functional component with a class based component.
Inside the functional component, I have a button that triggers a fetch call. After this fetch call is complete, I want the state of the class based (parent) component to update.
My approach was to make a function in the class based component (called setSubscriptoin) that adjusts the state of the class based component, then pass that function down to the functional component through props and call the function with a .then promise after the fetch call.
However, It appears that when I pass down the function through props, the functional component is not even able to detect the function and I get this error:
Uncaught (in promise) TypeError: e.setSubscription is not a function.
Here is the important code:
The class based component:
class OuterComponent extends React.Component {
constructor(props) {
super(props);
this.state = {subscription: {}}
}
setSubscription(subscription) {
this.setState({subscription: subscription})
}
render() {
return(
<Elements>
<FunctionComponent setSubscription={this.setSubscription.bind(this)}></FunctionComponent>
</Elements>
)
}
}
I wanted to include the elements part because I'm not sure if that could be effecting it. The FunctionComponent is wrapped inside a Stripe Elements provider. Not sure why that would do anything but I figured I should include it just in case.
The functional component:
const FunctionComponent = (props) => {
const fetchSomething = () => {
fetch('fetch is made here and is successful')
.then(response => {
if (some_condition) {
props.setSubscription({value1: 1, value2: 2}))
}
}
}
}
The problem is that the function component doesn't even recognize props.setSubscription as a function (as the error says).
I've tried console logging the props variable, and it does in fact have the function setSubscription in it so I have no clue what the issue could be. I've been trying to figure this out and am completely stumped. Does anyone know why this error is happening?
then should have a callback try this :
const FunctionComponent = (props) => {
const fetchSomething = () => {
fetch('fetch is made here and is successful')
.then(()=>props.setSubscription({value1: 1, value2: 2}))
}
}

What should I do to render sum of array objects values from API call

In this.state.companiesIncome, I have an array of 50 objects that have a {value and date}, but when I'm trying to console.log(this.state.companiesIncome[2].value) I'm receiving TypeError: Cannot read property '2' of null.
What I'm doing wrong?
import React, { Component } from 'react'
import './CompanyTotalIncome.css'
import axios from 'axios'
class CompanyIncome extends Component {
constructor(props) {
super(props)
this.state = {
companyID: props.companyID,
companiesIncome: null
}
}
componentWillMount() {
axios.get(`https://API/${this.state.companyID}`).then(res => {
this.setState({ companiesIncome: res.data.incomes })
})
}
render() {
console.log(this.state.companiesIncome[2].value)
return (
<>
<th>Total income</th>
</>
)
}
}
export default CompanyIncome
componentWillMount has been deprecated.
Use componentDidMount instead of componentWillMount to make the AJAX Call.
componentDidMount() {
axios.get(`https://API/${this.state.companyID}`).then(res => {
this.setState({ companiesIncome: res.data.incomes });
});
}
And place a check before using this.state.companiesIncome
this.state.companiesIncome && console.log(this.state.companiesIncome[2].value);
You're seeing the error before the axios.get will fire an AJAX call which is asynchronous by nature. And your render method will anyway get called before componentDidMount. So by the time the log gets called, this.state.companiesIncome would still be null.
Once componentDidMount is called, the API data is fetched and setState is called, it will trigger a re-render and the render method will be called again. This time with companiesData set. So it would work as expected.
Here's a Working Code Demo Example for your ref.
Because at the initial state your companiesIncome is null, companiesIncome[2] will throw an error. To avoid this, you can do the following:
this.state.companiesIncome && this.state.companiesIncome[2].value
This way, it will be printed only if you have a valid companiesIncome value. To improve it a bit you can also write:
(this.state.companiesIncome && this.state.companiesIncome.length >= 2) && this.state.companiesIncome[2].value
If you only want to log it to the console, do the following:
console.log(this.state.companiesIncome ? this.state.companiesIncome[2].value : 'companiesIncome is null');
The fact is that React starts rendering at the beginning. You can look in JSFiddleExample, in the console you will see a render at the beginning, then componentDidMount. Therefore, you must add validation. this.state.companiesIncome! == null. When you will change the state in the componentDidMount, the render starts again with your data.
You can read about React Lifecycle in React documentation

ReactJS ComponentWillMount() after passing Props

I want to ask why the child ( ComponentWillMount() ) component is only once rendered, once I am passing props to it everytime on onClick.
Once I click some button that is passing props to the child, the ComponentWillMount() of child is not triggering again, only in the first click only.
Parent Component:
render(){
return(
<div>
<AppModuleForm
editID = {this.state.editID}
editURL = {this.state.editURL}
editConf = {this.state.editConf}
editDesc = {this.state.editDesc}
editIcon = {this.state.editIcon}
editParent = {this.state.editParent}
editOrder= {this.state.editOrder}
status={this.state.status}
moduleList={this.state.moduleList}
updateAppModuleTree={this.updateAppModuleTree.bind(this)}/>
</div>
)
}
Child Component:
constructor(props){
super(props)
console.log(this.props.editDesc)
this.state={
url:'',
description:'',
parentID:'',
order:'',
configuration:'',
icon:'',
parentIDList:[],
urlDuplicate: false,
isSuccess: false,
errorMessage: '',
}
}
componentWillMount(){
if(this.props.status==='edit'){
let {
editURL,
editDesc,
editParent,
editConf,
editIcon,
editOrder} = this.props
this.setState({
url:editURL,
description:editDesc,
parentID:editParent,
order:editOrder,
configuration:editConf,
icon:editIcon,
})
}
}
componentWillReceiveProps(nextProps){
if(nextProps.status != this.props.status){
if(this.props.status==='edit'){
let {
editURL,
editDesc,
editParent,
editConf,
editIcon,
editOrder} = this.props
this.setState({
url:editURL,
description:editDesc,
parentID:editParent,
order:editOrder,
configuration:editConf,
icon:editIcon,
})
}
}
}
ComponentWillMount is mounting lifecycle method which will be called before mounting your component hence initialisation can be done in that while ComponentWillReceiveProps will be called once props are changed and you will get changes in nextProps parameter.
First you need to read https://reactjs.org/docs/state-and-lifecycle.html
and understand where to use props and why you need to pass something into component state.
From http://lucybain.com/blog/2016/react-state-vs-pros/
So when would you use state?
When a component needs to keep track of information between renderings
the component itself can create, update, and use state.
So you shouldn't transfer to state anything that will not change internally during component live cycle. As I can see all props those you pass to component are most likely will not be changed from within the component, all callbacks and icons you should take from props in component jsx.
If you have some editable data that you pass into its props from parent, on component mount (use componentWillMount()) you can copy that data to component state.That means all data will be stored in component internally and will not being overwritten on every render() call from passed props.
If you need to check if new props contains changes you can use componentWillReceiveProps(newProps) and there you can compare newProps with this.props and and process changes if needed.
Also i can suggest you to rename component callbacks handlers with respect to naming best practices:
<div>
<AppModuleForm
handleEditID = {this.onEditID}
handleEditURL = {this.onEditURL}
handleEditConf = {this.onEditConf}
handleEditDesc = {this.onEditDesc}
handleEditIcon = {this.onEditIcon}
handleEditParent = {this.onEditParent}
handleEditOrder= {this.onEditOrder}
status={this.state.status}
moduleList={this.state.moduleList}
updateAppModuleTree={this.updateAppModuleTree.bind(this)}/>
</div>
And I dont see any reasonable purpose to declare or to store functions in components state. So you can consider to move your handlers this.state.editID
etc. to parent component this scope. Like that
onEditId = () => { /* function code */ }
If you use arrow function = () => it automatically binds to component this and you don't need to bind them manually like you do in
{this.updateAppModuleTree.bind(this)}
After all that may be you will understand more clearly how you should manage your components life cycle and your problem will no longer be relevant.

implementation of component did mount in stateless component

Im new to react, i'm trying to implement/learn stateless component, Im having difficulty in using component will mount in stateless component.
My code
const Terms = (actions, commonReducer) => {
componentDidMount() {
actions.userActions()
}
return (
<div className="jobUpdate">
<form onSubmit={(e) => {
e.preventDefault(); actions.userInput(document.getElementById('enteredVal').value)
}}>
<input type="text" id="enteredVal" />
<button type="submit"></button>
</form>
</div>
);
};
I know the stateless component does not have life cycle hooks, but wanted alternate approach to preform component did mount in stateless component.
Any help with this is much appreciated. Thanks in advance
You could always wrap the component in another component, using a pattern known as higher-order components.
A higher-order component (HOC) is a function that takes a component and returns a new component.
Perhaps the most widely used example of this technique is react-redux, which uses the connect() method to create components connected to the redux store.
Instead of creating your own HOC, there are also libraries out there that can do it for you, such as react-redux-lifecycle.
However, what you are attempting with this component is not a very common pattern -- it is much more common to instead keep the handling of business and data in a container component, and leave presentational components to inherit store actions and data from props. Check out Dan Abramov's Presentational and Container Components for a very good overview on how and why to break down components into these two categories!
Starting in React 16.8, you can accomplish the same kind of functionality using a useEffect hook.
In your specific example, we'd have something like this:
import React, { useEffect } from 'react';
// other imports and declarations
function Example() {
// Similar to componentDidMount
useEffect(() => {
// This function will be run on component mount
myAction();
}, []); // The second argument of [] tells react to only perform the effect on mount
return (
<div>
... your component
</div>
);
}
export default Example;
The docs do a great job of explaining this, and I'd encourage you to read up on it. Keep in mind that it's not exactly the same thing going on behind the scenes, and so the patterns will not be a one-to-one correspondence; but these patterns should help with the majority of your cases.
Just know the following basic ideas:
The first argument of a useEffect hook is a "side effect" function. It is always run after the first component's render, and then conditionally afterwards.
This "side effect" function can return a "cleanup" function. The "cleanup" function is run just before the next time the "side effect" function is run. It will always be run before unmounting the component.
The second, optional, argument of a useEffect hook is an array of dependencies.
If any value in the dependency changes, the "side effect" function will be run after the next render.
Anyway, in the meantime, here's a few patterns to emulate class component behavior.
componentDidMount + componentDidUpdate
useEffect(() => {
console.log("This line will be run after each render ");
});
componentDidUpdate when a given value changes
// get myValue from component props
const { myValue } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue changed");
}, [myValue]);
componentDidUpdate when a given value changes, pt. 2
// get myValue from component props
const { myValue } = props;
const myCondition = myValue === "yes";
useEffect(() => {
console.log('This line will be run after each render where the returned value of the statement `myValue === "yes"` changes from false to true or true to false ');
}, [myCondition]);
componentDidUpdate when a given value changes, pt. 3
// get myValue from component props
const { myValue, myValue2 } = props;
useEffect(() => {
console.log("This line will be run after each render where myValue OR myValue2 changed");
}, [myValue, myValue2]);
componentDidMount
useEffect(() => {
console.log("This line will be run only after the first render ");
}, []);
componentWillUnmount
useEffect(() => {
// nothing will be run as a side effect...
return () => {
// but this will be run as clean up
console.log("This line will be run just before the component unmounts");
};
}, []);
componentDidUpdate (without componentDidMount)
import React, {useEffect, useRef} from 'react';
export default function MyComponent() {
// create a reference value which does not trigger a re-render when changed
const isMounted = useRef(false);
useEffect(() => {
if( isMounted.current === false ){
// on mount, set the ref to true
isMounted.current = true;
} else {
// the component is already mounted
console.log("This line will be run after each render except the first ");
}
});
return (<div />);
}
Hope this will be useful to someone.
Throw a dumb work around using HOC (high order component)
const withLifecycles = (MyStatelessComp) => class extends React.PureComponent {
static propTypes = {}
static displayName = "withPure(xxx)"
state = {}
componentDidMount() {}
render() {
return <MyStatelessComp {..this.state, ...this.props} />
}
}
then use it as
MyStatelessWithLifecycles = withLifecycles(props => {
...
return <Bla />
}
Though not sure what's the reason for a stateless component to have the lifecycles there, it's meant to be pure and simple (presentation only).

Categories

Resources