React Setstate callback called but render delayed - javascript

I just started to learn to react 2 days ago and I'm having a hard time with react's setstate method, all I know is use revstate parameter if want to change state based on previous state, and callback parameter to be executed right after the state change (please correct me if this wrong), so I just change the array content (which I render it using javascript's array.map) and I wish it renders right after the state is changed, it is changing but delayed, it only render after I do another click but the render method is called
for any senpai out there thanks for the help.
Handle click for changing to render content based on index passed on my button "onClick"
class App extends React.Component {
constructor(props){
super(props)
this.state = {
clickeditem : -1
}
this.torender = [
{
display : "first",
content : []
},
{
display : "second",
content : []
}
]
}
handleclick = (i) =>{
this.setState(prevstate=>{
if (prevstate.clickeditem === -1) {
return {clickeditem : i}
} else {
return prevstate.clickeditem === i ? {clickeditem : -1} : {clickeditem : i}
}
},() => {
return this.state.clickeditem === -1 ? (this.torender[0].content = [], this.torender[1].content = [])
: (this.state.clickeditem === 0) ? (this.torender[0].content = ["torender-0 content","torender-0 content"],this.torender[1].content = [])
: (this.state.clickeditem === 1) ? (this.torender[1].content = ["torender-1 content","torender-1 content"],this.torender[0].content = [])
: null
})
}
render(){
return(
<div>
<ul>
{
this.torender.map((item,index) => {
return(
<li key = {index}>
{item.display}
<ul>
{item.content.map((content,contentindex) => {
return(<li key = {contentindex}>{content}</li>)
})}
</ul>
</li>
)
})
}
</ul>
<button onClick={()=>this.handleclick(0)}>first-button</button>
<button onClick={()=>this.handleclick(1)}>second-button</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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="root"></div>

Refactor your code and Approach the simpler way
Actually, you shouldn't use the second param callback.
Whenever the state is changed, the life cycle of React Js will re-render it properly (See the image below to clarify in detail ^^!)
There are some things to note:
Move the content of each item in torender accordingly --> This is clearer about the initial data as well as it should not be mutated.
Default clickeditem is one of the items in torender, for example, the first item.
After that, you just control the content to be rendered in this way
___________ The condition to call renderContent() method ______________
{index === this.state.clickeditem && this.renderContent(item)}
_____________renderContent() looks like below_____________
renderContent = (item) => {
return (
<ul>
{item.content.map((content, contentindex) => {
return <li key={contentindex}>{content}</li>;
})}
</ul>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
clickeditem: 0
};
this.torender = [
{
display: "first",
content: ["torender-0 content", "torender-0 content"]
},
{
display: "second",
content: ["torender-1 content", "torender-1 content"]
}
];
}
handleclick = (index) => {
this.setState({clickeditem: index});
};
renderContent = (item) => {
return (
<ul>
{item.content.map((content, contentindex) => {
return <li key={contentindex}>{content}</li>;
})}
</ul>
);
};
render() {
return (
<div>
<ul>
{this.torender.map((item, index) => {
return (
<li key={index}>
{item.display}
{index === this.state.clickeditem && this.renderContent(item)}
</li>
);
})}
</ul>
<button onClick={() => this.handleclick(0)}>first-button</button>
<button onClick={() => this.handleclick(1)}>second-button</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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="root"> </div>

Issue
this.torender isn't part of react state, the component would need to rerender once more to see the mutation you did in the previous render cycle.
Solution
It is best to just compute it's value when you are rendering your UI then it should work as I suspect you meant it to.
handleclick = (i) =>{
this.setState(prevstate=>{
if (prevstate.clickeditem === -1) {
return { clickeditem: i }
} else {
return prevstate.clickeditem === i ? { clickeditem: -1 } : { clickeditem: i }
}
})
}
render(){
const { clickeditem } = this.state;
let torender = [
{
display : "first",
content : []
},
{
display : "second",
content : []
}
];
if (clickeditem === -1) {
torender[0].content = [];
torender[1].content = [];
} else if (clickeditem === 0) {
torender[0].content = ["torender-0 content","torender-0 content"];
torender[1].content = [];
} else if (clickeditem === 1) {
torender[1].content = ["torender-1 content","torender-1 content"];
torender[0].content = [];
} else {
torender = []; // <-- render nothing
}
return(
<div>
<ul>
{torender.map((item,index) => {
return(
<li key = {index}>
{item.display}
<ul>
{item.content.map((content, contentindex) => (
<li key={contentindex}>{content}</li>;
))}
</ul>
</li>
)
})
}
</ul>
<button onClick={()=>this.handleclick(0)}>first-button</button>
<button onClick={()=>this.handleclick(1)}>second-button</button>
</div>
)
}
}

Related

Show and hide looped elements in ReactJs

I loop through an array of elements:
this.props.connections.map((connection) => (
For each element in this array a card is created. In this card, I implemented a toogle button:
<div id="bookmarkIcon">
{this.state.available ? (
<Tab onClick={this.handleChange} icon={<StarBorderIcon/>}
aria-label="StarBorder"/>) : <Tab onClick={this.handleChange} icon={<StarIcon/>}
aria-label="StarIcon"/>}
</div>
The handle change method changes the value of available to false. The problem is that then I change the state and therefore, ever icon toggles, but I just want to toggle the icon I clicked on. How can I achieve this?
You can create an object which keeps the state as keys.
Here is a working example:
hidden will look something like this {0: true, 1: true, 2: false}
so we can update the corresponding items by their index.
https://codesandbox.io/s/intelligent-black-83cqg?file=/src/App.js:0-577
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [hidden, setHidden] = useState({});
const list = ["aa", "bb", "cc", "dd"];
const toggleHide = index => {
setHidden({ ...hidden, [index]: !hidden[index] });
};
return (
<div className="App">
{list.map((item, index) => (
<div>
{!!hidden[index] && <span>[HIDDEN]</span>}
{!hidden[index] && <span>[VISIBLE]</span>}
{item} <span onClick={e => toggleHide(index)}>x</span>
</div>
))}
</div>
);
}
Class-Based Component
class PrivacyPolicyDetails extends Component {
constructor(props) {
super(props);
this.state ={
resultData:[],
error:false ,
active: false,
activeIdList:[]
}
this.toggleClass.bind(this);
}
componentDidMount() {
setting.getQuestionAnswerByType('privacy-policy')
.then(res =>{
if(res.data.questionAnswerList.length > 0){
this.setState({
resultData: res.data.questionAnswerList,
})
}else{
this.setState({
error:true
});
}
}
);
}
toggleClass(id) {
const currentState = this.state.active;
this.setState({ active: !currentState});
if(this.state.activeIdList.find(element => element == id)){
this.state.activeIdList = this.state.activeIdList.filter(item => item !== id);
}else{
this.state.activeIdList.push(id);
}
}
render() {
const { product, currency } = this.props;
const {resultData,error,activeIdList} = this.state;
return (
<div>
<h1>Privacy Policy</h1>
{resultData && resultData.length > 0 ? resultData.map(each_policy =>
<div className="item">
<div className="question"
onClick={() => this.toggleClass(each_policy.question_answer_repository_id)}
>
{each_policy.question}
</div>
<p className={(activeIdList.find(element => element == each_policy.question_answer_repository_id))? "active-div" :"hide"}>
<div className="answer">{each_policy.answer}</div>
</p>
</div>
):''}
</div>
);
}
}
const mapStateToProps = (state) => {
return state.setting;
};
export default connect(mapStateToProps)(PrivacyPolicyDetails);
css
.hide{
display: none;
overflow:hidden;
}
.active-div{
display: block;
overflow:hidden;
}
Make the card into its own component and implement the state of the toggle inside of that component. In your parent component just map each card into one of these components. Each card will have its own toggle which uses the state of the card to determine how it should display.

Render multiple components without changing route path

My goal is to render a Component without changing its url pathname. This Component is responsible to render three other child components when user clicks on the link. My code is working, but I don't think it is the right approach. My question is: What would be the best way of implementing this. Here's my code below:
const ComponentOne = () => {
return (
<div><h1>Component 1</h1></div>
)
}
const ComponentTwo = () => {
return (
<div><h1>Component 2</h1></div>
)
}
const ComponentThree = () => {
return (
<div><h1>Component 3</h1></div>
)
}
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
linkName: 'three'
};
this.renderList = <ComponentThree/>;
}
handleClick = (linkName) => {
if (linkName === 'one') {
this.renderList = <ComponentOne/>;
this.setState({ linkName });
} else if (linkName === 'two') {
this.renderList = <ComponentTwo />;
this.setState({ linkName });
} else {
this.renderList = <ComponentThree />;
this.setState({ linkName });
}
return this.renderList;
};
render() {
return (
<div>
<ul>
<li><a
className={this.state.linkName === 'one' ? 'active' : ''}
onClick={() => this.handleClick('one')}>
Component 1
</a></li>
<li><a
className={this.state.linkName === 'two' ? 'active' : ''}
onClick={() => this.handleClick('two')}>
Component 2
</a></li>
<li><a
className={this.state.linkName === 'three' ? 'active' : ''}
onClick={() => this.handleClick('three')}>
Component 3
</a></li>
</ul>
{this.renderList}
</div>
)
}
}
ReactDOM.render(<Header/>, document.querySelector('#root'))
to better demonstrate here is a link to codepen:
codepen
Your code can be simplified by keeping the index in the state and then rendering based on the current index. Here's my slightly simplified solution:
https://codepen.io/olavih/pen/zYGobaV
return (
<div>
<h1>Component 1</h1>
</div>
);
};
const ComponentTwo = () => {
return (
<div>
<h1>Component 2</h1>
</div>
);
};
const ComponentThree = () => {
return (
<div>
<h1>Component 3</h1>
</div>
);
};
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
linkIndex: 3
};
}
handleClick = linkIndex => {
this.setState({ linkIndex });
};
render() {
return (
<div>
<ul>
<li>
<a
className={this.state.linkIndex === 1 ? "active" : ""}
onClick={() => this.handleClick(1)}
>
Component 1
</a>
</li>
<li>
<a
className={this.state.linkIndex === 2 ? "active" : ""}
onClick={() => this.handleClick(2)}
>
Component 2
</a>
</li>
<li>
<a
className={this.state.linkIndex === 3 ? "active" : ""}
onClick={() => this.handleClick(3)}
>
Component 3
</a>
</li>
</ul>
{this.state.linkIndex === 1 && <ComponentOne />}
{this.state.linkIndex === 2 && <ComponentTwo />}
{this.state.linkIndex === 3 && <ComponentThree />}
</div>
);
}
}
ReactDOM.render(<Header />, document.querySelector("#root"));
<a> tag by default will reload the page.
You can pass the event object from onClick action (onClick={(e) => this.handleClick(e,'three')}) to your handleClick method and in that method block reloading of the page with e.preventDefault().
You can also use React Router to switch from using default HTML <a> to <Link>. Link component is rendering <a> but it does not reload the entire page after clicking.

Toggle Two Things Separately React Constructor

I'm trying to toggle two different dropdown menus and can't seem to get it working. New to react and have probably been looking at it too long and it's something simple. The problem is when I toggle one the other gets toggled as well, so they both show.. Here is what I have:
import React from "react";
import { Link } from "./component/link";
import styles from "./header.module.css";
class Header extends React.Component {
constructor(props) {
super ( props )
this.state = {
show : false
}
this.toggleBusiness = this.toggleBusiness.bind(this);
this.state = {
show : false
}
this.togglePersonal = this.togglePersonal.bind(this);
}
toggleBusiness = () => {
const { show } = this.state;
this.setState( { show : !show } )
}
togglePersonal = () => {
const { show } = this.state;
this.setState( { show : !show } )
}
render() {
return (
<div className={ styles.topNav} >
<div className="grid">
<div className="grid-cell">
<div className={ styles.logoText }>
Logo
</div>
</div>
<nav>
<div className="grid-cell">
<ul className="list-unstyled">
<li><Link to={'/design'}>About</Link></li>
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.toggleBusiness}>Personal</a></li>
<li><Link to={'/posts'}>Blog</Link></li>
<li><Link to={'/contact'}>Contact</Link></li>
<li className={styles.menuButton}><a className="button button-secondary" href="tel:2252931086">File a Claim</a></li>
<li className={styles.menuButton}><a className="button" href="/">Get Insurance</a></li>
</ul>
</div>
</nav>
</div>
{ this.state.show && <BusinessDropdown /> }
{ this.state.show && <PersonalDropdown /> }
</div>
)}
}
class BusinessDropdown extends React.Component {
render () {
return (
<div className="dropdown">BusinessTest</div>
)
}
}
class PersonalDropdown extends React.Component {
render () {
return (
<div className="dropdown">PersonalTest</div>
)
}
}
export default Header;
So basically I want it to toggle the Business Dropdown one when I click Business and the Personal Dropdown when I press Personal. Also, if you have something that would work better than this approach let me know!
Change your toggleBusiness and togglePersonal to this:
toggleBusiness = () => {
const { show } = this.state;
this.setState({ show: show === "business" ? null : "business" });
};
togglePersonal = () => {
const { show } = this.state;
this.setState({ show: show === "personal" ? null : "personal" });
};
then in the conditional rendering, do this:
{this.state.show === "business" && <BusinessDropdown />}
{this.state.show === "personal" && <PersonalDropdown />}
...also in your links, you have this:
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.toggleBusiness}>Personal</a></li>
Where you should have this:
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.togglePersonal}>Personal</a></li>
Edit: I realise this is not quite what you asked for - this toggles business off when personal is switched on. Personally I think this approach would actually suit better given the fact you're opening a new dropdown menu, you will probably want the other one to close.
You are using this.state.show for both Business and Personal.
Adding a second state variable like showBusiness and showPersonal
should solve your problem.
Also you can/should declare your state only once with
this.state = {
showBusiness: false,
showPersonal: false
};
In your constructor you set this.state.show to false 2 times separate this into two separate variables, perhaps this.state.showBuisness and this.state.showPersonal?
My approach would be something like this.
1. set initial state
constructor(props){
super(props);
this.state={
business: false,
personal: false
}
}
2. Create ONE function to update both status.
handleClick = (e) => {
this.setState(prevState => {
[e.target.id]: !prevState[e.target.id]
}
}
3. Add function to the onclick AND an id
<button id="personal" onClick={this.handleClick}>SHOW PERSONAL</button>

React click on item to show details

Iam new to React and I'm trying to interact with the swapi API.
I want to get the list of films (movie titles list) and when I click on a title to show the opening_crawl from the json object.
I managed to get the film titles in an array. I don't know how to proceed from here.
Here is my code:
class StarWarsApp extends React.Component {
render() {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
}
class Header extends React.Component {
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
class Movies extends React.Component {
constructor(props) {
super(props);
this.handleMovies = this.handleMovies.bind(this);
this.state = {
movies: []
};
this.handleMovies();
}
handleMovies() {
fetch("https://swapi.co/api/films")
.then(results => {
return results.json();
})
.then(data => {
console.log(data);
let movies = data.results.map(movie => {
return <div key={movie.episode_id}>{movie.title}</div>;
});
this.setState(() => {
return {
movies: movies
};
});
});
}
render() {
return (
<div>
<h1>Episodes</h1>
<div>{this.state.movies}</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("app"));
To iterate over movies add this in render method:
render(){
return (
<div>
<h1>Episodes</h1>
{
this.state.movies.map((movie, i) => {
return (
<div className="movie" onClick={this.handleClick} key={i}>{movie.title}
<div className="opening">{movie.opening_crawl}</div>
</div>
);
})
}
</div>
);
}
Add this method to your Movies component to add active class on click to DIV with "movie" className:
handleClick = event => {
event.currentTarget.classList.toggle('active');
}
Include this css to your project:
.movie .opening {
display: none;
}
.active .opening {
display: block
}
After fetching the data, just keep it in your state then use the pieces in your components or JSX. Don't return some JSX from your handleMovies method, just do the setState part there. Also, I suggest using a life-cycle method (or hooks API maybe if you use a functional component) to trigger the fetching. By the way, don't use class components unless you need a state or life-cycle methods.
After that, you can render your titles in your render method by mapping the movies state. Also, you can have a place for your opening_crawls part and render it with a conditional operator. This condition changes with a click. To do that you have an extra state property and keep the movie ids there. With the click, you can set the id value to true and show the crawls.
Here is a simple working example.
const StarWarsApp = () => {
const title = "Star Wars";
const subtitle = "Movies";
return (
<div>
<Header title={title} />
<Movies />
</div>
);
}
const Header = ({ title }) => (
<div>
<h1>{title}</h1>
</div>
);
class Movies extends React.Component {
state = {
movies: [],
showCrawl: {}
};
componentDidMount() {
this.handleMovies();
}
handleMovies = () =>
fetch("https://swapi.co/api/films")
.then(results => results.json())
.then(data => this.setState({ movies: data.results }));
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
render() {
return (
<div>
<h1>Episodes</h1>
<div>
{this.state.movies.map(movie => (
<div
key={movie.episode_id}
id={movie.episode_id}
onClick={this.handleCrawl}
>
{movie.title}
{this.state.showCrawl[movie.episode_id] && (
<div style={{ border: "1px black solid" }}>
{movie.opening_crawl}
</div>
)}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(<StarWarsApp />, document.getElementById("root"));
<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="root"></div>
I am using id on the target div to get it back from the event object. I don't like this method too much but for the sake of clarity, I used this. You can refactor it and create another component may be, then you can pass the epoisde_id there and handle the setState part. Or you can use a data attribute instead of id.

React: clicking on an item in a list and print out its current state

I am mapping over a list of objects in state and creating a list.
However, I'd like to add some functionality that will allow me to change the state of one of the items in the list.
In the code below, I have a helper function called handleEdit(e). In this function, I'd like to print out its state. IE - {name:'Eric', update: false}
What should I put in there to achieve this?
function AddPerson(props) {
return(
<div>
<input type="text" value= {props.newPerson} onChange = {props.handleUpdate}/>
<button type="submit" onClick= {props.addNewFriend}> Add New </button>
</div>
)
}
function Person(props) {
console.log(props.handleEdit)
return (
props.listOfPeople.map((person, i) => {
return(
<li key={i} onClick = {props.handleEdit}>{person['name']}</li>
)
})
)
}
function ListPeople(props) {
return(
<div>
<ul>
<Person listOfPeople = {props.people} handleEdit = {props.edit}/>
</ul>
</div>
)
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
newPerson: '',
people: [{name:'Eric', update: false} , {name:'Rick', update:false}, {name:'Yoni', update:false}]
};
this.handleUpdate = this.handleUpdate.bind(this)
this.addNewFriend = this.addNewFriend.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleUpdate(e) {
this.setState({newPerson: e.target.value})
}
addNewFriend(){
console.log(this.state.newPerson)
const newFriendList = this.state.people.slice()
this.setState(
{
newPerson: '',
people: newFriendList.concat({name:this.state.newPerson, update:false})
}
)
}
handleEdit(e) {
console.log(e.target.value)
return null
}
render() {
return (
<div>
<AddPerson handleUpdate = {this.handleUpdate} addNewFriend = {this.addNewFriend} newPerson = {this.state.newPerson} />
<ListPeople people = {this.state.people} edit={this.handleEdit} />
</div>
);
}
}
In your Person component list pass the onClick handler like this
props.listOfPeople.map((person, i) => {
return(
<li key={i} onClick = {(e) => {props.handleEdit(e,person)}}>{person['name']}</li>
)
})
And then in handleEdit the second argument is the person value you want
handleEdit(e, person) {
console.log(person)
return null
}
Replace this line <Person listOfPeople = {props.people} handleEdit = {props.edit}/> with <Person listOfPeople = {props.people} handleEdit = {props.edit.bind(this, this.state)}/>
and change method as
handleEdit(state) {
console.log(state);
return null
}
To get the data on your grandparent component you can pass it through the function itself.
Example
// change your function to get person object with the event object together
handleEdit(event, person) {
console.log(event.target.value);
console.log(person);
return null
}
// In your Person component pass the event and person objects to the function
function Person(props) {
console.log(props.handleEdit)
return (
props.listOfPeople.map((person, i) => {
return(
<li key={i} onClick={(event) => props.handleEdit(event, person)}>
{person['name']}
</li>
)
})
)
}

Categories