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

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>

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>
)
}
}

How to handle multiple material-UI Popovers [React.js]?

My app has multiple Popover components, I know how to handle the state of one Popover component, using something like this:
class App extends Component {
constructor(props) {
super(props);
this.state = { pop_open: false };
}
handleProfileDropDown(e) {
e.preventDefault();
this.setState({
pop_open: !this.state.pop_open,
anchorEl: e.currentTarget,
});
}
handleRequestClose() {
this.setState({
pop_open: false,
});
};
render() {
return (
<div>
<button type="submit" onClick={this.handleProfileDropDown.bind(this)} >My Customized PopOver</button>
<Popover
open={this.state.pop_open}
anchorEl={this.state.anchorEl}
onRequestClose={this.handleRequestClose.bind(this)}
>
{"content"}
</Popover>
</div>
);
}
}
But for more than one Popover, I do not know how to do that, should I create a state for each Popover? Sorry for the question but I am new to the frontend world.
note: kindly do not use hooks in your answer.
An internal state is a good option when only the Component is going to modify it. It keeps the logic simple and inside the same block of code. On the other hand managing the state from outside of the Component lets other components read its values and modify them. This is a common approach when using Redux or Context, where there is a global app state. This state is meant for properties that several Components need to read/write to.
Which to use when is a design decision and depends on each situation. In my opinion each Component should handle its own state when possible. For example, when values are only going to be modified by it, or a children Component. Having an external state makes sense when multiple Components are going to read or modify it, or when the state values need to be passed several levels deep in the hierarchy.
In the example you propose I can see that the Popover is working with an internal state. This can work and you can use the Component several times and it will carry all the logic inside. If you rename the Components you can see more easily what I mean. I dont know exactly how the Component with the button works but this is to make the explanation clear:
class Popover extends Component {
constructor(props) {
super(props);
this.state = { is_open: false };
}
open = () => {
this.setState({
is_open: true
});
}
close = () => {
this.setState({
is_open: false
});
}
toggle = () => {
this.setState(prevState => ({
is_open: !prevState.is_open
}));
}
render() {
return (
<div>
<button onClick={this.toggle}>
Open
</button>
{this.state.is_open && <PopoverContent />}
</div>
);
}
}
If you need further explanation or something is not clear, let me know.

How to read (decipher) this strings?

All code implements adding or removing classes(toggle).
I commented strings.
class Example extends React.Component {
state = {
isActive: false,
};
handleClick = () => {
this.setState(state => ({ isActive: !state.isActive })); // stirng №1
};
render() {
const { isActive } = this.state; // stirng №2
return (
<div>
<button onClick={this.handleClick}>Try it</button> //stirng №3
<div className={isActive ? 'mystyle' : ''}> //stirng №4
This is a DIV element.
</div>
</div>
);
}
}
How to read (decipher) this strings? Not only explanations of what they do, but how they are read
handleClick = () => {
this.setState(state => ({ isActive: !state.isActive })); // stirng №1
};
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
This is using the "updater function form", it executes a callback function with how the state should be modified. In this case, it's setting "isActive" to the inverse of the current state value of "isActive".
It's also using a short form of Arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions Read up on that there
const { isActive } = this.state; // stirng №2
This is using destructuring to "unpack" the "isActive" value from the state (the state is an object). So instead of accesing the value using "this.state.isActive", now you can just use "isActive".
<button onClick={this.handleClick}>Try it</button> //stirng №3
"onClick" is a Javascript event handler, basically it says that "when this button is clicked, execute this function". The function being "handleClick" defined above that toggles the "isActive" state.
Hope that helps!

React Parent component checkbox state updates with one step delay

I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});

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