When talking about pure render methods in React and how setting state inside of render is a serious anti-pattern, how strictly we are speaking about this? I get that if I do a setState inside a render function, which doesn't require any user input, it can create infinite loops and re-renders.
What I don't understand is, does this rule apply also when writing a page element that receives input from user? I mean, what's the difference between these two ways to write a simple click-handler for a button:
render() {
return(
<div className="container-fluid info-modal">
<div className="row">
<div className="col col-12">
<InfoModal active={this.state.modalActive}>
<h2>Fine dining recipes</h2>
<p>Here you can publish your fine dining recipes. Don't forget to
include every ingredient needed!</p>
<Button
title="ok"
onClick={() => {
this.setState({ modalActive: false })
}}
/>
</InfoModal>
</div>
</div>
</div>
)
}
vs.
render() {
return(
<div className="container-fluid info-modal">
<div className="row">
<div className="col col-12">
<InfoModal active={this.state.modalActive}>
<h2>Fine dining recipes</h2>
<p>Here you can publish your fine dining recipes. Don't forget to
include every ingredient needed!</p>
<Button
title="ok"
onClick={() => {
this.closeModal()
}}
/>
</InfoModal>
</div>
</div>
</div>
)
}
I get that for a more complicated components, probably the right way to do this would be to use a click-handler which this class receives as an property, but for simple use cases, are there really some concrete harm in setting the state inside an inline click-handler?
When docs say that you must not setState within render it means that that the setState function must not be called as soon as we render and not that setState cannot be written inside render
so when you write
<Button
title="ok"
onClick={() => {
this.setState({ modalActive: false })
}}
/>
You are not actually calling setState in render but you are calling it on click action of button which are 2 different things
So whether you write
<Button
title="ok"
onClick={() => {
this.closeModal()
}}
/>
or
<Button
title="ok"
onClick={() => {
this.setState({ modalActive: false })
}}
/>
are equivalent if you just setState inside closeModal
A call that will not be accepted is below
<Button
title="ok"
onClick={this.setState({ modalActive: false })}
/>
Related
I can access JSON objects in the results here
useEffect(() => {
fetch(`/user/${userid}`,{
method:'get',
headers:{
"Authorization":`Bearer ${localStorage.getItem('token')}`}
}).then(res=>res.json())
.then(result=>{
console.log(result.user.name) //I can access JSON objects in the results here//
setProfile(result)
})
.catch(err=>console.log(err))
}, [])
I can access JSON while Changing State but it throws errors like UserProfile.user is undefined,UserProfile.posts.length is undefined when rendering JSX. TO access the nested Data I have tried creating more state Variables. It works but the code have become long. I looked for solution in various website but could not find. Any one help me.
return (
<>
{
UserProfile?<div style={{maxWidth:'800px',margin:"0px auto"}}>
<div style={{
display:"flex",
justifyContent:'space-around',
margin:"18px 0px"
}}>
<div>
<img style={{borderBottom:"1px solid grey",width:"160px",height:"160px",borderRadius:"80px"}} src="https://images.unsplash.com/photo-1569466896818-335b1bedfcce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60"/>
</div>
<div>
<h4>{UserProfile?UserProfile.user.name:"Loading..."}</h4>
<div style={{display:"flex",justifyContent:"space-between",width:"100%"}}>
<h6>{UserProfile.posts.length} posts </h6>
<button onClick={()=>followUser()} className="btn waves-effect waves-light #64b5f6 blue lighten-2" type="button" name="action">Follow</button>
</div>
</div>
</div>
<div className="gallery">
{
UserProfile.posts.map((item)=>{
return(
<img className="item" key={item._id} src={item.photo}/>
)
})
}
</div>
</div>:
<h1>Loading:</h1>
}
</>
)
export default Profile
Based on the code and your inputs, the problem may be because you are trying to access the variables before they are accessible.
As you are making async API call in useEffect() it may take some time before you get data. But, as you are accessing the data before you even get it errors like 'UserProfile.user is undefined', 'UserProfile.posts.length' is undefined will occur'
To avoid such errors make sure you add a check as shown below
<>
{
UserProfile &&
<div style={{maxWidth:'800px',margin:"0px auto"}}>
<div style={{
display:"flex",
justifyContent:'space-around',
margin:"18px 0px"
}}>
<div>
<img style={{borderBottom:"1px solid grey",width:"160px",height:"160px",borderRadius:"80px"}} src="https://images.unsplash.com/photo-1569466896818-335b1bedfcce?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60"/>
</div>
<div>
/* -----> modify it here*/ <h4>{UserProfile ? UserProfile.user && UserProfile.user.name:"Loading..."}</h4>
<div style={{display:"flex",justifyContent:"space-between",width:"100%"}}>
/* -----> modify it here*/ <h6>{UserProfile && UserProfile.posts && UserProfile.posts.length} posts </h6>
<button onClick={()=>followUser()} className="btn waves-effect waves-light #64b5f6 blue lighten-2" type="button" name="action">Follow</button>
</div>
</div>
</div>
<div className="gallery">
{
UserProfile.posts.map((item)=>{
return(
<img className="item" key={item._id} src={item.photo}/>
)
})
}
</div>
</div>
:
<h1>Loading:</h1>
}
</>
I got the solution for this. I got rid of this problem by setting initial state to null. Thank You for answering my query.
//my initial useState declaration//
const [userProfile,setUserProfile]=useState([])
//Solution//
const [userProfile,setUserProfile]=useState(null)
I am new to react so I got into problem.
This has never happened to me before I tried changing props in all different ways, names and values, I have even tried making a method for each button. the problem is that the buttons don't always do what they are suppossed to, sometimes they increment the "session" or decrement the "break" or vice versa
const breakdown="breakdown"
const breakup="breakup"
const sessionup="sessionup"
const sessiondown="sessiondown"
class Clock extends React.Component{
constructor(){
super()
this.state={
break:5,
session:25,
vid:false
}
this.handleChange=this.handleChange.bind(this)
}
handleChange(e){
if(e.target.value===breakup){
this.setState({
break:this.state.break+1
})
}else if(e.target.value ==breakdown){
this.setState({
break:this.state.break-1
})
}else if(e.target.value==sessionup){
this.setState({
session:this.state.session+1
})
}else if(e.target.value==sessiondown){
this.setState({
session:this.state.session-1
})
}
}
render(){
return(
<div>
<div id="title"><h1>Pamodoro Clock</h1></div>
<div id="timing">
<div id="break">
<div id="break-label">Break Length</div>
<div className="control">
<button id="break-decrement" value={breakdown} onClick={this.handleChange}>
<i className="fa fa-arrow-down"/>
</button>
<div id="break-length">{this.state.break}</div>
<button id="break-increment" value={breakup}onClick={this.handleChange}>
<i className="fa fa-arrow-up"/>
</button>
</div>
</div>
<div id="session">
<div id="session-label">Session Length</div>
<div className="control">
<button id="serssion-decrement" value={sessiondown}onClick={this.handleChange}>
<i className="fa fa-arrow-down"/>
</button>
<div id="session-length">{this.state.session}</div>
<button id="session-increment"value={sessionup}onClick={this.handleChange}>
<i className="fa fa-arrow-up"/>
</button>
</div>
</div>
</div>
</div>
)
}
}
From the React documentation:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
It means when you use this.setState({something: this.state...}) several times, there is not guaranteed that the last call will be invoked with last state.
Therefore to prevent unexpected results, just use updater:
this.setState(state => ({
break: state.break+1
}))
I am trying to set a simple search operation in a user interface as shown below:
I have a total of 70 react-strap cards and each card contain a vessel with name, type and an image. I would like to search the name of the vessel and have the card related to that vessel to pop-up. All my images are currently contained inside the external database Contentful. Below the fields of interests:
The problem is that I don't know how to write a search function that locate a specific value of a list.
Below the code:
SideBar.js
import React from 'react';
import Client from '../Contentful';
import SearchVessel from '../components/SearchVessel';
class Sidebar extends React.Component {
state = {
ships: [],
};
async componentDidMount() {
let response = await Client.getEntries({
content_type: 'cards'
});
const ships = response.items.map((item) => {
const {
name,
slug,
type
} = item.fields;
return {
name,
slug,
type
};
});
this.setState({
ships
});
}
getFilteredShips = () => {
if (!this.props.activeShip) {
return this.state.ships;
}
let targetShip = this.state.ships.filter(
(ship) => this.props.activeShip.name === ship.name
);
let otherShipsArray = this.state.ships.filter((ship) => this.props.activeShip.name !== ship.name);
return targetShip.concat(otherShipsArray);
};
render() {
return (
<div className="map-sidebar">
{this.props.activeShipTypes}
<SearchVessel />
<pre>
{this.getFilteredShips().map((ship) => {
console.log(ship);
return (
<Card className="mb-2">
<CardImg />
<CardBody>
<div className="row">
<img
className="image-sizing-primary"
src={ship.companylogo.fields.file.url}
alt="shipImage"
/>
</div>
<div>
<img
className="image-sizing-secondary"
src={ship.images.fields.file.url}
alt="shipImage"
/>
</div>
<CardTitle>
<h3 className="thick">{ship.name}</h3>
</CardTitle>
<CardSubtitle>{ship.type}</CardSubtitle>
<CardText>
<br />
<h6>Project Details</h6>
<p>For a description of the project view the specification included</p>
</CardText>
<Row style={{ marginTop: '20px' }}>
<div className="buttoncontainer">
<div className="btn btn-cards">
<a
className="buttonLink"
download
href={ship.projectnotes.fields.file.url}
>
Project Notes
</a>
</div>
<div className="btn btn-cards">
<a className="buttonLink" href={ship.abstract.fields.file.url}>
Abstract
</a>
</div>
</div>
</Row>
</CardBody>
</Card>
);
})}
</pre>
</div>
);
}
}
export default Sidebar;
VesselSearch.js
import React, { Component } from 'react';
export default class SearchVessel extends Component {
render() {
const { value, handleSubmit, handleChange } = this.props;
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-10 mx-auto col-md-8 mt-5 text-center">
<h4 className="text-slanted text-capitalize">Search for Vessel</h4>
<form className="mt-4" onSubmit={handleSubmit}>
<label htmlFor="search" className="text-capitalize">
type vessel separated by comma
</label>
<div className="input-group">
<input
type="text"
name="search"
placeholder="Type name of vessel here"
className="form-control"
value={value}
onChange={handleChange}
/>
<div className="input-group-append">
<button type="submit" className="input-group-text bg-primary text-white">
<i className="fas fa-search" />
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
}
What I have done so far:
1) I tried different combination with the filter function and I think I am close. The problem is that when I operate the search nothing happens and in order to find the card of the vessel I want, I have to scroll down until I find it.
I am running out of ideas and if you see something I didn't catch point me in the right direction for solving this issue.
You're close! I would add a field to your state called 'searchText' and then create a method to filter based on that searchText state item.
getFilteredShips = () => this.state.ships.filter(s => s.name.includes(this.state.searchText)
Then just map over those values to render the cards that match the search text. The cards will update each time the searchText value updates.
this.getFilteredShips().map(ship => ..........
React is famous for re-usable component. You will have all the data of these vessels in an array. You will loop through the array and render the items with card component.And when you search for the specific card you want that vessel to pop out on top.
There are two ways to do it:
You have to run through the array, find the index of that vessel and do whatever it takes to manipulate your array and to make that item at top and re-render your list.
Alternatively render one more component on top of your vessel list as user clicks the search button. You just have to find the item index and render it. This way you don't have to deal with array manipulation. It doesn't matter if you have 80 or 1000 cards.
Please checkout official documentation for array methods, for array slicing and splice.
Hope this is what you are looking for. If you need further help, comment please.
Ok, working on my second ever React app. In this project, I am trying to use nested components (my last one just had 1 component). I've got a Button component that I am trying to render within the main App component, which is a calculator (Here is my design I am replicating in node/react locally: https://codepen.io/ryanmdoyle/pen/QBvjdw)
So far in my code, the only button I am actually implementing so far is the AC button. Everything else is copy/paste from my codepen design. I know some aspects like the CSS classes and values are getting passed around correctly because it displays the CSS properly, but I am trying to take the value of the Button component and use that within methods in the App component. Particularly in the handleInput method. I know with an input field you can get the event.target.value but that's obviously not working!
import React, { Component } from 'react';
// import './App.css';
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={this.handleInput}>{props.value}</button>
class App extends Component {
constructor(props) {
super(props);
this.state = {
resultDisplay: "",
entryDisplay: "",
}
this.handleInput = this.handleInput.bind(this);
}
handleInput(event) {
console.log(event.target.value);
this.setState({
entryDisplay: this.state.entryDisplay + event.target.value
})
}
render() {
return (
<div className="grid-container">
<div className="display">display</div>
<Button id="clear" class={`button button-secondary`} value="ac" />
<div id="plus-min" className="button button-secondary">+/-</div>
<div id="percent" className="button button-secondary">%</div>
<Button id="one" class="button button-primary" value={1} />
<div id="two" className="button button-primary">2</div>
<div id="three" className="button button-primary">3</div>
<div id="four" className="button button-primary">4</div>
<div id="five" className="button button-primary">5</div>
<div id="six" className="button button-primary">6</div>
<div id="seven" className="button button-primary">7</div>
<div id="eight" className="button button-primary">8</div>
<div id="nine" className="button button-primary">9</div>
<div id="zero" className="button-primary">0</div>
<div id="divide" className="button button-operator">/</div>
<div id="multiply" className="button button-operator">*</div>
<div id="subtract" className="button button-operator">-</div>
<div id="add" className="button button-operator">+</div>
<div id="decimal" className="button button-primary">.</div>
<div id="equals" className="button button-operator">=</div>
</div>
);
}
}
export default App;
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={this.handleInput}>{props.value}</button>
You are trying to add a function that is not local to Button.
For using it you need to pass the function like this
<Button id="clear" class={`button button-secondary`} value="ac" click={this.handleInput}/>
And when you try to use the function in Button use it like this
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={() => props.click()}>{props.value}</button>
One thing to remember this is null in a arrow function by default.
You must know that when ever you are trying to create a component where stateless or stateful doesnot matter its function set, variable must be separate. You cannot use it like you have tried in this case.
class in React is just like classes in other programming language almost.
Currently, in your Button component:
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={this.handleInput}>{props.value}</button>
this.handleInput isn't defined. It is defined in your App component but not in Button. If you try clicking the Button, you will get an error in the console.
What you're looking to do is pass a callback to your Button component from the App component via props:
In App:
<Button handleInput={this.handleInput} />
In Button:
<button onClick={props.handleInput}
Replace these line in your code
Button Component
const Button = (props) => <button id={props.id} className={props.class} value={props.value} onClick={props.onClick}>{props.value}</button>
Pass props in all button Component like this
<Button id="clear" class='button button-secondary' value="ac" onClick={this.handleInput}/>
Remember that you can pass entire functions as props to your components, which is the beauty of JavaScript and React! So, essentially, write the function to clear your screen in your main App component, and then pass a reference down to your button component. Attach the function to the onClick event for your button component.
// App.js
...
clearScreen() => this.setState({ resultDisplay: '', entryDisplay: ''})
...
<Button id="clear" class={`button button-secondary`} value="ac" onClick={clearScreen.bind(this)} />
// ButtonComponent.js
...
<button onClick={this.props.onClick} />
...
You will need to pass a function as a prop down to your child component, which will then allow you to alter the parent's functionality.
For example:
passFunction(value) {
// Modify passed parameter (value) from child and implement in parent
this.setState({value: value});
// or call that method that you want to call.
handleInput(value);
}
<Button passFunction={this.passFunction} ... />
Then in Button.js or wherever the child component is:
this.prop.passfunction(someValueToParent)
I have:
JobScreen
handleSetView(mode, e) {
this.setState({
view: mode
});
console.log(this.state.view)
}
render() {
return (
<div className="jobs-screen">
<div className="col-xs-12 col-sm-10 job-list"><JobList view={this.state.view} /></div>
<div className="col-xs-12 col-sm-2 panel-container">
<div className="right-panel pull-right"><RightPanel handleSetView={this.handleSetView} /></div>
...
)
}
RightPanel
render() {
return (
<div>
<div className="controls">
<span className="title">Views <img src="images\ajax-loader-bar.gif" width="24" id="loader" className={this.state.loading ? "pull-right fadeIn" : "pull-right fadeOut"}/></span>
<button onClick={this.props.handleSetView.bind(this, 'expanded')}><img src="/images/icons/32px/libreoffice.png" /></button>
<button onClick={this.props.handleSetView.bind(this, 'condensed')}><img src="/images/icons/32px/stack.png" /></button>
</div>
...
)}
JobList
render() {
var jobs = [];
this.state.jobs.forEach((job) => {
jobs.push(
<Job key={job.id} job={job} view={this.props.view} loading={this.state.loading} toggleTraderModal={this.props.toggleTraderModal} toggleOFTModal={this.props.toggleOFTModal}/>
);
});
return (
<div>
{jobs}
</div>
);
};
The problem is, is that the changing of the view state does not rerender any of the child elements.
How can I get this to work?
Not sure if it is the core of your problem, but:
handleSetView={this.handleSetView}
is wrong, because how js binds this. Use:
handleSetView={this.handleSetView.bind(this)}
instead.
Also,
this.setState({
view: mode
});
console.log(this.state.view)
seems strange; note that this.state is not modified right after you call setState, it may took some time while React dispatches the scheduled setState operation. Put that console.log into render to see when it is actually called.
Finally, make sure, your components do not implement shouldComponentUpdate lifecycle method (you are probably not doing this explicitly, but if your component extends some class other than React.Component this may happen)
this is in the context of the react Component so either pass the reference of this to you function handleSetView or bind this as mentioned by #Tomas