How come I can't update my state? - reactjs

class ProductCarousel extends Component {
constructor (props) {
super(props)
this.state = {
active: -1,
fullScreen: false
}
this.flky = {}
}
componentDidMount (props) {
this.flky = new Flickity('.carousel', flickityOptions)
this.flky.on('select', () => {
this.setState({ active: this.flky.selectedIndex })
})
}
fullScreen () {
this.setState({ fullScreen: true })
console.log(this.state)
}
render () {
const images = this.props.images
return (
<div>
<div className='carousel'>
{images.map((url, i) => (
<img
className='productPhoto'
key={i.toString()}
onClick={this.fullScreen.bind(this)}
src={stripUrl(url)}
style={{maxWidth: '100%'}}
/>
))}
</div>
</div>
)
}
}
I have this component in js, in the function fullScreen, I expect this.state.fullScreen to be true, but it's still false, why?

setState is asynchronous. Its second parameter is a callback that you can use to react to when the state was actually updated.

setState does not update the state immediately. It create a pending transition instead.
If you want to check the component's state just after setState, do it like this:
this.setState({fullScreen: true}, () => {
// I am triggered after the state is set
console.log(this.state)
})
Read about it over here

Related

React states remain undefined

I'm trying to set the in property of a bootstrap <Collapse> tag to true on a button click. But when I try to reference my is_open state its undefined.
class Graph extends Component {
constructor(props) {
super (props);
this.state = ({
is_open: false,
});
}
click_open = () => {
console.log(this.is_open); // logs undefined
this.setState({ is_open: !this.is_open });
}
render() {
return (
<div className='container>
<button onClick={this.click_open}>TAB</Button>
<Collapse in={this.is_open}></Collapse>
</div>
)
})
}
No matter what I do my state stays undefined. What am I missing here?
You're missing state
Change your code to be:
click_open = () => {
console.log(this.state.is_open);
this.setState({ is_open: !this.is_open });
}

What is the correct way to call API inside a Modal, when it's visible in React?

Suppose that I have a Modal inside a Component and I want to show the Modal when some buttons are clicked:
render(){
...
<Modal
is={this.state.productId}
visilble={this.state.visible}
/>
}
Inside the Modal Component, I want to call API to get the product detail based on the selected id as the following:
componentWillReceiveProps(nextProps) {
if(nextProps.visible && !this.props.visible) {
fetch(...).then(res => {
this.setState({
product: res.data
})
}).catch(err => {
...
})
}
}
From the React docs it says that componentWillReceiveProps, componentWillUpdate is deprecated and you should avoid them in new code.So I try use static getDerivedStateFromProps()
static getDerivedStateFromProps()(nextProps) {
if(nextProps.visible && ...) {
fetch(...).then(res => {
return {
product: res.data
}
}).catch(err => {
...
})
} else return null (or just return null here without else)
}
The above code doesn't work since fetch is asynchronous so it always returns null or doesn't return anything, you can't use await here to wait for the api to resolve also, and I heard that getDerivedStateFromProps shouldn't use for data fetching.
So what is the best way solve the problem ?
I think it's better to decide whether to show Modal component or not in parent component as Modal component should be a functional component to render only modal related view. This way every time Modal component will not be rendered and only rendered when visible flag is true.
{ this.state.visible &&
<Modal />
}
In parent component you could fetch data in componentDidMount if just after initial render the data is required or componentDidUpdate if after every update the fetch data is required for modal. After fetching data set state of visible to true.
Happy Coding!!!
You can mount the Modal based on this.state.visible and start fetching when Modal is mounted on componentDidMount or when the props are changing through componentDidUpdate
// delay
const delay = () => new Promise(res => setTimeout(res, 1000));
// fake products
const products = [
{ id: 1, text: "product 1" },
{ id: 2, text: "product 2" },
{ id: 3, text: "product 3" }
];
// fake ajax call
const API = async productId => {
await delay();
return products.find(p => p.id === productId);
};
class Parent extends Component {
state = { productId: 1, visible: false };
toggleShow = () => {
this.setState(prevState => ({ visible: !prevState.visible }));
};
setProductId = productId => this.setState({ productId, visible: true });
render() {
return (
<div className="App">
<button onClick={this.toggleShow}>show or hide</button>
<button onClick={() => this.setProductId(1)}>fetch product 1</button>
<button onClick={() => this.setProductId(2)}>fetch product 2</button>
<button onClick={() => this.setProductId(3)}>fetch product 3</button>
<button onClick={() => this.setProductId(4)}>unknown product id</button>
{this.state.visible && <Modal is={this.state.productId} />}
</div>
);
}
}
class Modal extends Component {
state = { product: null, fetching: false };
componentDidMount = () => {
this.fetchProduct();
};
componentDidUpdate = prevProps => {
if (prevProps.is !== this.props.is) {
this.fetchProduct();
}
};
fetchProduct = async () => {
this.setState({ fetching: true });
const product = await API(this.props.is);
this.setState({ product, fetching: false });
};
render() {
const { product, fetching } = this.state;
if (fetching) return <h1>{`fetching product ${this.props.is}`}</h1>;
return product ? (
<div>
<h1>{`product id: ${product.id}`}</h1>
<h3>{`text: ${product.text}`}</h3>
</div>
) : (
<h1>Product not found</h1>
);
}
}
SandBox
Actually in another approach, you can use <modal/> in another pureComponent
(for example: componentContainer) and just call it in your main view. and use just one object inside your main view state as your <componentContainer/> data scope and give it as a property like this code
construcor(prop){
super(props);
this.state={
productDetail:null<<change this in data fetch
}
}
<componentContainer data={this.state.productDetail} modalVisible={this.state.changeNumderModal}/>
and inside your component container:
<Modal
animationType="fade"
transparent={true}
visible={this.props.modalVisible}//<<<<<<<<<
onRequestClose={() => {
}}>
//contents:
this.props.data.map(()=>//-----)
</View>
</Modal>
in this method you have just one modal and one scope as its data, you can call the fetchs and other functions in your main component as others... and if its so needed you can even pass a function as property to modal container to :
someFunction(someval){
//do some thing
}
<componentContainer data={this.state.productDetail} modalVisible={this.state.changeNumderModal} someFunction={(val)=>this.someFunction(val)}/>
and call it inside :
this.props.someFunction('what ever you want')
So Let's assume you did that hide and show of modal with button click, now whenever the model will open
inside the componentWillMount function
class Modal extends Component {
this.state = {product:[], loader : false}
componentWillMount(){
fetch(...).then(res => {
this.setState({
product: res.data
})
}).catch(err => {
...
})
}
render(){
const {product, loader} = this.state;
return (
if (loader) return <ProductComponent data = {product}/>
else return <div> No data found </div>
);
}
}
You can use the approach below:
componentDidUpdate(prevProps, prevState, snapshot) {
if (this.props.visible && !prevProps.visible) {
// make the API call here
}
}

React: Issues with Conditional Rendering

In my React-App, i use the Firebase SDK. If a user wants to reset his password, he will be redirected to a page within my app. If the code is valid, the component <PWResetConfirmForm /> should be rended. If the code is invalid, the component <PWResetOutdatedForm /> is to be rendered.
My Page Component looks like this:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset(code)
.then(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
})
.catch(function () {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
})
}
render() {
return (
<div>
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage
When i try to run, i get a blank page and not error.
Where is my issue and how can i fix that?
Thank you very much for your help and for your time
You will not be able to return JSX from within then()/catch() of auth.doVerfiyPasswordReset() like that. You can instead approach this by taking advantage of React.Component lifecycle method componentDidMount and using setState() to manipulate state properties for conditional rendering. I've added state properties to the component, one to track whether loading (API call has completed) and one to track whether the call was a success (then) or failure (catch). These properties are used to conditionally generate JSX content for rendering. This is assuming that verfiyResetPassword() is intended to run when the component is first mounted, instead of every time render() is called:
class App extends Component {
constructor() {
super();
this.state = {
isResetVerified: null,
loading: true
};
}
componentDidMount() {
this.verfiyResetPassword();
}
verfiyResetPassword() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
auth.doVerfiyPasswordReset('foobar')
.then(() => {
this.setState({
...this.state,
isResetVerified: true,
loading: false
});
})
.catch(() => {
this.setState({
...this.state,
isResetVerified: false,
loading: false
});
})
}
getContent() {
if (this.state.loading) {
return (
<div>Loading...</div>
);
} else {
if (this.state.isResetVerified) {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetConfirmForm></PWResetConfirmForm>
</div>
);
} else {
return (
<div className="HomePage-Main">
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
}
Here is a basic example in action.
Also, in the constructor this.verfiyResetPassword = this.verfiyResetPassword.bind(this); would only be needed if verfiyResetPassword() is executed by a DOM event such as button onClick or similar.
Hopefully that helps!
I could still fix the error myself:
class PWResetConfirmPage extends Component {
constructor(props) {
super(props);
this.state = {
isValid: false,
code: "",
};
this.verfiyResetPassword = this.verfiyResetPassword.bind(this);
}
componentDidMount() {
const params = (new URL(`http://dummy.com${this.props.location.search}`)).searchParams;
const code = params.get("oobCode")
this.setState({code:code})
auth.doVerfiyPasswordReset(code)
.then(() => {
this.setState({
...this.state,
isValid: true,
});
})
.catch(() => {
this.setState({
...this.state,
isValid: false,
});
})
}
verfiyResetPassword() {
if (this.state.isValid) {
return (
<div>
<TopBar></TopBar>
<PWResetConfirmForm code={this.state.code}></PWResetConfirmForm>
</div>
);
} else {
return (
<div>
<TopBar></TopBar>
<PWResetOutdatedForm></PWResetOutdatedForm>
</div>
);
}
}
render() {
return (
<div className="HomePage-Main">
{this.verfiyResetPassword()}
</div>
);
}
}
export default PWResetConfirmPage

All the toasters close when clicked on the close button in react

I have made a toaster component of my own which on multiple clicks render multiple toasters. The problem I am facing is that all the toasters are terminated when the handle close component is clicked or when the settimeout function is called. I am passing messages through another component as props.
This is my toaster component
export default class MyToaster extends React.Component {
constructor(props) {
super(props);
this.state = {
message: props.message,
show: false,
no: 0
};
}
handleclose = () => {
this.setState({
show: false,
no: this.state.no - 1
})
}
handleOpen = () => {
console.log('HANDLE OPEN')
this.setState({
show: true,
no: this.state.no + 1
}, () => {
setTimeout(() => {
this.setState({
show: false,
no: this.state.no - 1
})
}, 3000)
})
}
createtoaster = () => {
if (this.state.show) {
let toastmessage = [];
for (let i = 0; i < this.state.no; i++) {
let tmessage = <div className="snackbar">
<div className="card-header">
<h3 className="card-title">Toast</h3>
</div>
<div className="card-body">
{this.state.message}
</div>
<div className="card-footer"></div>
<button className="btn" onClick={this.handleclose}>x</button>
</div>
toastmessage.push(tmessage);
}
return toastmessage;
} else {
return null;
}
};
render() {
return (
<div className="col-md-2 offset-md-9">
<button className="btn btn-primary" onClick={this.handleOpen}></button>
{this.createtoaster()}
</div>
)
}
}
I have tried managing the state in the parent component but it doesnt seem to work. I do know that the problem is in managing state of my toaster component but dont know the exact problem and the solution.
Any solutions for this also feel free to point out any of my mistakes.
TIA
Handle close is run on the click of any button rather on the instance of one of them by the looks of it.
if (this.state.show) { // this determines whether to render you toasts...
// and close turns all of them off.
You need to change each toast to have it's own show property and for close to toggle that one and remove it from the array of toasts to generate.
Note:
Your props and state should be separate, don't copy props into state as this will introduce bugs and changes will not be reflected.
constructor(props) {
super(props);
// avoid copying props into state
// https://reactjs.org/docs/react-component.html#constructor
this.state = {
message: props.message,
show: false,
no: 0
};
}
There is a different way to this approach.
export default class MyToaster extends React.Component {
constructor(props) {
super(props);
this.state = {
message: props.message,
show: true,
no: 0
};
}
componentDidMount() {
setTimeout(() => {
this.setState({show: false})
}, 4000)
}
handleclose = () => {
this.setState({
show: false,
no: this.state.no - 1
})
}
handleOpen = () => {
this.setState({
no: this.state.no + 1
}, () => {
setTimeout(() => {
this.setState({
show: false,
no: this.state.no - 1
})
}, 3000)
})
}
render() {
return (
<div className="col-md-2 offset-md-9">
{this.state.show
? (
<div className="container snackbar" style={this.props.style}>
<div className="card-header">
<h3 className="card-title">Toast</h3>
</div>
<div className="card-body">
{this.props.message}
</div>
<div className="card-footer"></div>
</div>
)
: null
}
</div>
)
}
}
And from your parent component you can include
this.state = {
toasterCollection: []
}
//make a function
handleToasterClick = () => {
const toaster = <Toaster message={this.message} style={this.style}/>
this.setState({
// toasterCollection: [...this.state.toasterCollection, toaster]
toasterCollection: [...this.state.toasterCollection, toaster]
});
}
//In your Render give a button
<button className="btn btn-primary" onClick={this.handleToasterClick}>
Toast
</button>
//Also render this
{this.state.toasterCollection}
This should get your code to work.

React force componentDidMount

I have the following:
import React from 'react';
import axios from 'axios';
class FirstName extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
};
}
getName () {
var name = this.refs.firstName.value;
this.setState(function() {
this.props.action(name);
});
}
handleSubmit (e) {
e.preventDefault();
this.setState({ submitted: true }, function() {
this.props.actionID(2);
this.props.activeNav('color');
});
}
render () {
return (
<div>
<h2>tell us your first name</h2>
<form>
<input
type="text"
ref="firstName"
onChange={this.getName.bind(this)}
/>
<div className="buttons-wrapper">
<button href="#">back</button>
<button onClick={this.handleSubmit.bind(this)}>continue</button>
</div>
</form>
</div>
);
}
};
class PickColor extends React.Component {
backToPrevious (e) {
e.preventDefault();
this.props.actionID(1);
this.props.activeNav('name');
}
goToNext (e) {
e.preventDefault();
this.props.actionID(3);
this.props.activeNav('design');
this.props.displayIconsHolder(true);
}
getColorValue(event) {
this.props.color(event.target.getAttribute("data-color"));
}
render () {
var colors = ['red', 'purple', 'yellow', 'green', 'blue'],
colorsLink = [];
colors.forEach(el => {
colorsLink.push(<li
data-color={el}
key={el}
onClick={this.getColorValue.bind(this)}
ref={el}>
{el}
</li>
);
});
return (
<section>
<ul>
{colorsLink}
</ul>
<button onClick={this.backToPrevious.bind(this)}>back</button>
<button onClick={this.goToNext.bind(this)}>continue</button>
</section>
);
}
}
class ConfirmSingleIcon extends React.Component {
goBack () {
this.props.goBack();
}
confirmCaptionandIcon (event) {
var optionID = event.target.getAttribute("data-option-id"),
name = event.target.getAttribute("data-option-name");
this.props.setOptionID(optionID);
this.props.setIcon(1, name, optionID, false);
}
goNext () {
this.props.goNext();
}
render () {
console.log(this.props.currentState);
var options = [],
that = this;
this.props.iconOptionsList.forEach(function(el){
options.push(<li onClick={that.confirmCaptionandIcon.bind(that)} key={el.option} data-option-name={el.option} data-option-id={el.id}>{el.option}</li>);
});
return (
<div>
<h2>Choose your caption</h2>
<h3>
{this.props.selectedIcon}
</h3>
<ul>
{options}
</ul>
<button onClick={this.goBack.bind(this)} >back</button>
<button onClick={this.goNext.bind(this)} >confirm</button>
</div>
);
}
}
class ConfirmCaption extends React.Component {
handleClick () {
var currentState = this.props.currentState;
this.props.setIcon(currentState.icon_ID, currentState.selectedIcon, currentState.option_ID, true);
this.props.setIconVisiblity(true);
this.props.setIconListVisiblity(false);
}
render () {
console.log(this.props.currentState);
return (
<div>
<p onClick={this.handleClick.bind(this)}>confirm icon and caption</p>
</div>
);
}
}
class ChooseIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
setOptionID (id) {
this.setState({ option_ID: id })
}
setIconVisiblity (onOff) {
this.setState({ confirmIcon: onOff })
}
setIconListVisiblity (onOff) {
this.setState({ iconList: onOff })
}
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
handleClick (event) {
var iconId = event.target.getAttribute("data-icon-id"),
that = this;
this.state.icons.forEach(function(el){
if(el.id == iconId){
that.setState(
{
confirmIcon: true,
iconList: false,
selectedIcon: el.name,
icon_ID: iconId,
selectedIconOptions: el.option
}
);
}
});
}
goBack () {
this.setState(
{
confirmIcon: false,
iconList: true
}
);
}
goNext () {
this.setState(
{
confirmIcon: false,
iconList: false,
confirmCaption: true
}
);
}
render () {
var icons = [];
this.state.icons.forEach(el => {
icons.push(<li data-icon-id={el.id} onClick={this.handleClick.bind(this)} key={el.name}>{el.name}</li>);
});
return (
<div>
{this.state.iconList ? <IconList icons={icons} /> : ''}
{this.state.confirmIcon ? <ConfirmSingleIcon goBack={this.goBack.bind(this)}
goNext={this.goNext.bind(this)}
setIcon={this.props.setIcon}
selectedIcon={this.state.selectedIcon}
iconOptionsList ={this.state.selectedIconOptions}
setOptionID={this.setOptionID}
currentState={this.state} /> : ''}
{this.state.confirmCaption ? <ConfirmCaption currentState={this.state}
setIcon={this.props.setIcon}
setIconVisiblity={this.setIconVisiblity}
setIconListVisiblity={this.setIconListVisiblity} /> : ''}
</div>
);
}
}
class IconList extends React.Component {
render () {
return (
<div>
<h2>Pick your icon</h2>
<ul>
{this.props.icons}
</ul>
</div>
);
}
}
class Forms extends React.Component {
render () {
var form;
switch(this.props.formID) {
case 1:
form = <FirstName action={this.props.action} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 2:
form = <PickColor displayIconsHolder={this.props.seticonsHolder} color={this.props.colorVal} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 3:
form = <ChooseIcon setIcon={this.props.setOptionA} />
break;
}
return (
<section>
{form}
</section>
);
}
}
export default Forms;
"ChooseIcon" is a component that will get used 3 times therefore everytime I get to it I need to bring its state back as if it was the first time.
Ideally I would need to make this ajax call everytime:
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
is there a way to manually call componentDidMount perhaps from a parent component?
React handles component lifecycle through key attribute. For example:
<ChooseIcon key={this.props.formID} setIcon={this.props.setOptionA} />
So every time your key (it can be anything you like, but unique) is changed component will unmount and mount again, with this you can easily control componentDidMount callback.
If you are using the ChooseIcon component 3 times inside the same parent component, I would suggest you to do the ajax in componentDidMount of the parent component like this (exaclty how you have in your example, in terms of code)
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
and then pass this data down to the ChooseIcon component
render() {
return (
//do your stuff
<ChooseIcon icons={this.state.icons}/>
)
}
after this you will only need to set the received props in your ChooseIconcomponent, for that you only need to change one line in it's constructor:
constructor(props) {
super(props);
this.state = {
icons: props.icons, // Changed here!
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
The parent component can use a ref to call the function directly.
However, trying to force this function feels like a smell. Perhaps lifting the state higher up the component tree would solve this problem. This way, the parent component will tell ChooseIcon what to show, and there will not be a need to call componentDidMount again. Also, I assume the Ajax call can also occur once.

Resources