using a function within a component that gets removed - state transition warning - reactjs

This application works. I don't see any problems in performance, but I do get this warning.
The warning:
Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
The function within question:
getUserLogged = (newUser) => {
this.setState ({
userLogged : newUser,
isUserLogged : true
})
}
this is used within my main app
class App extends Component {
constructor() {
super();
this.state = {
users:[],
userLogged : null,
isUserLogged: false,
isAdmin : false,
};
}
Within my app component, I display 1 of 3 main components: OderPage, AdminPanel or LoginPanel.
If isUserLogged is null, we display
<div className="loginPrompt">
<LoginPage getUserLogged={this.getUserLogged} users = {this.state.users} getAdminLogged = {this.getAdminLogged}/>
</div>
Inside my login component, I display a pin panel that the user pressed and I compare the pins to those saved in the DB.
If they match, we call getUserLogged :
if (checkPin === tempPin) {
this.props.getUserLogged(user);
}
After that function is called, the main app's state changed, this rendering a new component (orderpage) and no longer rendering the old one (login).
This application. I don't see any problems in performance, but I do get this warning. Any suggestions?
EDIT (more detail):
The way my app component renders is it pushes the component to render into a array, displayed like so:
return (
<div className="App">
{whichRender}
</div>
)
the if statements that use getUserLogged and login panel:
var whichRender = [];
if (this.state.isUserLogged) {
whichRender.push (
<div className="AppContainer" key={this.state.userLogged.name}>
<MainNav user={this.state.userLogged.name} handleLogOut = {this.handleLogOut}/>
<OrderPage user={this.state.userLogged.name} handleLogOut = {this.handleLogOut}/>
</div>
)
} else {
whichRender.push (
<div className="AppContainer" key={'loginPromptApp'}>
<div className="loginPrompt">
<LoginPage getUserLogged={this.getUserLogged} users = {this.state.users} getAdminLogged = {this.getAdminLogged}/>
</div>
</div>
)
}

Related

Changing state in React JS is not re-rendering parent

My react app is a multi-page form. It goes to next page after clicking 'Next'. Currently I have some text that should have a css class when current page is page 1, and when user goes to next page, the css class should be removed for that text (the text is still displayed for all pages).
My actual code is much larger so I'm only posting all the important parts(I think) that are required for this questions.
import ChildComponent from '....';
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
page: page + 1 //to go to next page
});
this.setState({
currentPageis1: !currentPageis1
});
}
showPage = () =>{
const {page, currentPageis1} = this.state;
if(page === 1)
return (<ChildComponent
change={this.change}
currentPageis1={currentPageis1}
/>)
}
render(){
return (
<p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<form>{this.showPage()}
)
}
}
class ChildComponent extends React.Component {
someFunction = e =>{
e.preventDefault();
this.props.change();
}
render(){
return (
<Button onClick={this.someFunction}>Next</Button>
)
}
}
Currently, when I click Next button, the currentPageis1 updates to false. I checked it using Firefox React extension. But it does not re-render the page. Which means "Some Text" still has the CSS class.
My guess is className={this.currentPageis1 ? '': 'css-class'} in Parent class is only being run once (when the page is first loaded). Do I have to use lifecycle method? How do I make react re-render everytime currentPageis1 is changed?
You are doing <p className={this.currentPageis1 ? '': 'some-css-class'}>Some Text</p>. In order to apply styles to only page 1, you should revert the values in your condition. When currentPageis1 is false '' value is picked up.
Also this.currentPageis1 is wrong. You should use state i.e. this.state.currentPageis1
Working demo
Like this
<p className={this.state.currentPageis1 ? "some-css-class" : ""}>
Some Text
</p>
To get your style to render, you'll need to add the props keyword.
Return Child component inside of Parent and pass the change method as
a prop
Also, updated your setState so you only call it once instead of twice
in the change method
class Parent extends React.Component {
state = {
page: 1, //default start page
currentPageis1: true,
currentPageis2: false,
currentPageis3: false,
}
change = () => {
const = {page, currentPageis1} = this.state;
this.setState({
...this.state,
page: page + 1,
currentPageis1: !currentPageis1
});
}
render(){
return (
<div>
<p className={this.props.currentPageis1 ? '': 'some-css-class'}>Some Text</p>
<Child change={this.change} />
</div>
)
}
}

(React) How to match the modal content with the clicked element?

I'm trying to match the modal that shows with the clicked element, right now i'm rendering all the modals with the click, I been trying to make them match with the index but no luck , please help.
here are my constructors
constructor(props) {
super(props);
this.state = {
portfolioData: [],
newModal:[],
modalPost: false,
isShown: false
};
}
showModal = (i) =>{
this.setState({ isShown: true, modalPost: true })
}
closeModal = () => {
this.setState({isShown:false, modalPost: false})
}
and here I get the data and render two list component and the modal
componentDidMount() {
axios.get(`data.json`)
.then(res => {
const portfolioData = [res.data.portfolio.projects.film];
this.setState({ portfolioData });
})
};
the components
const portfolioList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<PortfolioItem
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
showModal={this.showModal}
youtube={val.trailer}
/>
))
const modalList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<Modal
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
closeModal={this.closeModal}
youtube={val.trailer}
/>
))
and the return
<section id="portfolio">
{ portfolioList }
{ this.state.modalPost !== false ? modalList : null }
</section>
Your code will make as many modal as the data held by this.state.portfolioData, which is unnecessary, inefficient and may result into wasted rendering. If you take a step back and think from this way, You are going to render only one modal but render it with the data of the selected item
Lets see an example,
We can start by having an additional state value selectedValue which will hold the clicked item's value
this.state = {
portfolioData: [],
newModal:[],
modalPost: false,
isShown: false,
selectedValue: {} //<---
};
Then, when the user clicks the item we can set that particular items value in the state; specifically in selectedValue
const portfolioList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<PortfolioItem
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
showData={() => this.showData(val)} //<---
youtube={val.trailer}
/>
))
//new function for one specific task <---
const showData = (value) => {
this.setState({selectedValue: value}, this.showModal)
}
Finally, instead of mapping over the data you can render only one modal which takes and show the data from the this.state.selectedValue
<Modal
id={this.state.selectedValue.title.en.toString().toLowerCase().split(" ").join("-")}
title={this.state.selectedValue.title.en}
imgsrc={this.state.selectedValue.imgsrc}
status={this.state.selectedValue.status}
profile={this.state.selectedValue.profile}
director={this.state.selectedValue.director}
production={this.state.selectedValue.production}
closeModal={this.closeModal}
youtube={this.state.selectedValue.trailer}
/>
This is merely an idea you can follow. You should organize/optimize your code later per your codebase afterwards. If you are unfamiliar with setStates second parameter it takes a callback as a second parameter which it executes after updating state. Reference: https://reactjs.org/docs/react-component.html#setstate
Hope this helps you, Cheers!
Edit: I just noticed you are not using that isShown anywhere. That is the value that the modal should be opened based on. The modal should have a prop which makes it show/hide by passing true/false, check the documentation. And you should pass this.state.isShown to that specific prop to make everything come together and work!
The problem is you are creating multiple instances of modals when you are looping over your portfolioData. I dont think you need the multiple instances since you will open only one at a time.
When you are setting the state of modal to isShown. You are actually setting state on all the instances of the generated modals. Hence you end up opening multiple modals. You should ideally have just one modal and pass data to your modal.
You can do this:
First, Move the modal out of the loop so that you have only one instance of it.
Pass data to:
<a>onClick={() => this.toggleModal(provider.data)} key={index}>Portfolio Item</a>
Lastly, in toggleModal function first set the data then open modal.
This way all your PortfolioItem links will end up calling the same modal instance. But with different data. Once you set the data you can rerender your component with the already existing isShown state.
Here is a small example:
This way all your PortfolioItem links will end up calling the same modal instance. But with different data. Once you set the data you can rerender your component with the already existing isShown state.
Here is a small example:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
isShown: false,
data: ''
}
this.list = [{data: 'data1'}, {data: 'data2'}];
}
onAnchorClick({data},event){
this.setState({data, isShown: true});
}
render(){
return(
<div>
{this.list.map((obj, idx) => <div key={idx}>
<a onClick={this.onAnchorClick.bind(this, obj)}>Portfolio Item</a>
</div>)}
<div style={{display: !this.state.isShown ? 'none' : 'block'}}>
<h3>Data: {this.state.data}</h3>
</div>
</div>
)
}
}
window.onload = () => {
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>

Unable to prevent rerender with shouldComponentUpdate() or with PureComponent

In /home/:id I have a <LogButtons/> when its clicked logOn() get called so logsignPopUp variable become a <logForm/> component.
In the same page I have a <IframeYoutubeComponent/>, I want to prevent it to rerender when the <logForm/> pop on the screen so the video isn't reloaded.
home.js :
export default class Home extends Component {
constructor(props) {
super(props);
this.state = { logsign: "" };
this.logOn = this.logOn.bind(this);
this.signOn = this.signOn.bind(this);
}
logOn() {
this.setState({ logsign: "log" });
}
render() {
let logsignPopUp = this.props.logsign === "log" ? <LogForm/> : this.state.logsign;
let homePage =
<div>
{logsignPopUp}
<div>
<LogButtons logOn={this.logOn}/>
</div>
<div>
<IframeYoutubeComponent paramsId={this.props.match.params.paramsId}/>
</div>
</div>;
return (
<div>
<Route exact path="/home/:id" component={() => <div>{homePage}</div> } />
</div>
);
}
}
iframeYoutubeComponent.js :
export class IframYoutubeComponent extends Component {
render() {
//this.props.youtube come from Redux state
let src = this.props.youtube.find(el => el.youtubeId === this.props.paramsId);
return (
<iframe src={"https://www.youtube.com/embed/" + src}></iframe>
);
}
}
I tried to return false in shouldComponentUpdate() but its not even called :
shouldComponentUpdate() {
console.log("test");
return false;
}
I tried to use a PureComponent for <IframeYoutubeComponent/> but the video still reload when the <logForm/> pop.
I tried to add key to my components and also tried to put this.state.logsign in Redux but nothing worked out.
I started react since 2 months so I might miss something obvious but I can't find out what... Any idea ?
That's because you are passing an arrow function in a component prop to the Route. This way everytime you generate a new function.
You should pass a react component in this prop or at least a function that returns JSX but this function should be defined once. For example as a class method.
Something like:
<div>
<Route exact path="/home/:id" component={this.renderHomePage} />
</div>
But then of course you have to refactor your logic regarding this logsign prop.

React onChange not working as intended

I'm trying to create a products component that get's all the products available on the website and displays each of them in sort of like a box and when the user clicks that box they get redirected to that product page. I'm using react and redux and I'm having a difficulty with onClick. This is how my code looks
class Products extends Component{
constructor(){
super();
this.state = {
products: [...Some array]
};
}
handleProductRedirect(productNumber){
console.log(productNumber)
// Redux function
// this.props.handleRedirect(productNumber)
}
render(){
var products = this.state.products
return (
<div id="content">
{product &&
<div id="searchContent">
{product.map(element => <Item element={element}
handleProductRedirect={this.handleProductRedirect.bind(this)}
key={element['productNumber']}/>)}
</div>
}
</div>
</div>
)
}
};
class Item extends Component{
render(){
var element = this.props.element;
return (
<div id="itemBox" onClick={this.props.handleProductRedirect(element['productNumber'])}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
)
}
}
so the component gets the products from an api and once it's get them it iterates through them. However, I noticed using chromes developer console that every that it iterates through every <Item /> component it calls handleProductRedirect even though that Item wasn't clicked on. It does it automatically. Instead of calling that function when the div itemBox is clicked on, it calls it when it's rendered. Any suggestions
That's because you are calling the handleProductRedirect on every render for each item. Instead of that, you need send the callback in the onClick prop, something like this:
class Item extends Component{
onClickItem = () => { // <=== Defines the callback and bind it to the instance
const { element } = this.props;
this.props.handleProductRedirect(element['productNumber']);
};
render(){
var element = this.props.element;
return (
<div id="itemBox" onClick={this.onClickItem}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
)
}
}
This way you are not calling the callback on every render, but when the user actually clicks element.
Also, don't forget to define the propTypes on your components, it really helps to catch issues later on.
Your onClick is calling the function here:
onClick={this.props.handleProductRedirect(element['productNumber'])}>
Instead you should return a function that calls it with the argument. You can do that by making an arrow function like this:
onClick={() => this.props.handleProductRedirect(element['productNumber'])}>
But the best way to do it is to extract it into a class method (so that you don't get unnecessary re-renders):
class Item extends Component {
clickProduct = () => {
this.props.handleProductRedirect(this.props.element['productNumber']);
}
render() {
var element = this.props.element;
return (
<div id="itemBox" onClick={this.clickProduct}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
);
}
}

When is the render() called in a class that extends Component?

What refreshes the view in react or is the code always live displayed?
I have a function called removeAdmin and makeAdmin which adds and removes users as Admins and then when a user is an admin the render of Member component renders and admin shield logo. It works fine but I'm wondering whether render is being triggered each time I change the UI using a function or if render is live listening to changes in it's components?
class MemberList extends Component {
constructor(props) {
super(props)
this.state = {
members: [],
loading: false,
administrators: []
}
this.makeAdmin = this.makeAdmin.bind(this)
this.removeAdmin = this.removeAdmin.bind(this)
}
componentDidMount(){
this.setState({loading:true})
fetch('https://api.randomuser.me/?nat=US&results=12')
.then(response => response.json())
.then(json => json.results)
.then(members => this.setState({
members,
loading:false
}))
}
makeAdmin(email){
const administrators = [
...this.state.administrators,
email
]
this.setState({administrators})
}
removeAdmin(email){
const administrators = this.state.administrators.filter(
adminEmail => adminEmail !== email
)
this.setState({administrators})
}
render() {
const { members, loading } = this.state
return (
<div className="member-list">
<h1>Society Members</h1>
{
(loading) ?
<span> loading...</span>:
<span>{members.length} members</span>
}
{ (members.length)?
members.map(
(member, i) =>
<Member key={i}
// This admin prop is worked out by enumerating through the administrator
// array with some(). some() passes in the enumerators, checking whether
// the current member in members.map() exists in the administrators array
// and returns admin=true if so.
admin={this.state.administrators.some(
adminEmail => adminEmail === member.email
)}
name={member.name.first + '' + member.name.last}
email={member.email}
thumbnail={member.picture.thumbnail}
makeAdmin={this.makeAdmin}
removeAdmin={this.removeAdmin}/>
):
<span>Currently 0 members</span>
}
</div>
)
and the Member component:
class Member extends Component {
componentWillMount(){
this.style={
backgroundColor: 'grey'
}
}
render() {
const { name, thumbnail, email, admin, makeAdmin, removeAdmin } = this.props
return (
<div className="member" style={this.style}>
<h1>{ name } {(admin) ? <FaShield/> : null}</h1>
<div>
<img src={ thumbnail }/>
</div>
<div>
{
(admin)?
<Button title="Make Admin" onClick={() => removeAdmin(email) } color="#841584"> Remove Admin </Button>
:
<Button title="Make Admin" onClick={ () => makeAdmin(email) } color="#841584"> Make Admin </Button>
}
<a href={`mailto:${ email }`}><p> {email} </p></a>
</div>
</div>
)
}
}
export default Member
What triggers a new render on components is when the state changes or when receiving new properties.
There are two main objects that drive the render in each component, the this.props and the this.state. If any of this objects gets updated then the render method gets executed.
The this.props object gets updated whenever you send new properties to the childrens. this.state gets updated using the this.setState method.
That being said, it's really important to keep track of the properties you send to the children, as a rule of thumb I always recommend not using the spread operator to pass props to the children, for example:
<Parent>
<Child {...this.props} />
</Parent>
I'd avoid that pattern because if any of the props changes, than all props are sent to the child. Instead I recommend sending only what the children needs.
<Parent>
<Child some={this.props.value} />
</Parent>
You need to be very careful when you need to render your component, otherwise it's so easy to re-render everything! Which will lead to performance issues.
It depends on what you define your component render method to be.
It can change based on the state or props that you give it.
Less frequent, but you can check out shouldComponentUpdate as it allows you to overwrite the method to give it more “smarts” if you need the performance boost.

Resources