How to check whether React updates dom or not? - reactjs

I wanted to check how to react does reconciliation so I updated the inner HTML of id with the same text. Ideally, it shouldn't update the dom but it is paint reflashing in chrome.
I have tried paint reflashing in chrome it is showing green rectangle over that same text
import React from 'react';
function App() {
return (
<div >
<p id="abc" key="help">abc is here</p>
<button onClick={function () {
// document.getElementById("may").innerHTML = "";
document.getElementById("abc").innerHTML = "abc is here";
}} > Btn</button>
</div>
);
}
export default App;
Expected result should be that paint reflashing shouldn't happen but it is happening.

You are not using React here to update the text of your p tag but directly updating the DOM with JavaScript.
So React reconciliation algorithm doesn't even run here.

In React, the output HTML is a result of the state and the props of your component.
When a change in state or props is detected, React runs the render method to check if it needs to update the DOM. So, in order to do this check, you need to store the parameters that determine your view in state or props.
Given your example, we could save the text you want to show in the p tag in the state of your component (using hooks):
import React, { useState } from 'react';
function App () {
const [text, setText] = useState('abc is here');
render() {
return (
<div >
<p id="abc" key="help">{this.state.text}</p>
<button onClick={() => setText('abc is here') }>Btn</button>
</div>
);
}
}
export default App;
If you are using a version of React that does not support hooks, you will need to transform your functional component into a class to use state:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = { text: 'abc is here' };
}
render() {
return (
<div >
<p id="abc" key="help">{this.state.text}</p>
<button onClick={() => this.setState({ text: 'abc is here' }) }>Btn</button>
</div>
);
}
}
export default App;

Related

How to prevent component from being re-rendered unnecessarily

I'll start with the code. I have a stateless functional component that resembles this
export const Edit Topic = (_title, _text) {
const [title, setTitle] = useState(_title)
const [text, setText] = useState(_text)
return (
<>
<InputText props={{ fieldName:"Title:", value:title, setValue:setTitle, placeHolder:"Topic Title"}}/>
<InputTextArea props={{ fieldName:"Markdown Text:", text, setText }}/>
<PreviewBox text={text}/>
</>
)
}
I have PreviewBox when it's on, page rendering takes a bit longer because text can be quite long. PreviewBox needs to re-render each time I change text in InputTextArea and that's fine.
The problem I'm having is when I change the value of title it's also updating <PreviewBox/> which is undesired.
How can I make sure that <PreviewBox/> only updates when text changes and not when title changes?
The reason why I believe the re-rendering is occuring is because if I toggle off PreviewBox, there's no lag in when updating title but when PreviewBox is visible the updating the title lags.
import style from "../styles/CreateTopic.module.css"
import { Component } from "react"
import Markdown from "./Markdown";
export class PreviewBox extends Component {
constructor(props) {
super(props)
this.state = {
isShow: true
}
}
toggleShow = () => {
console.log("begin isShow", this.state)
this.setState(state => ({ isShow: !state.isShow}))
}
render() {
return (
<>
<div className={style.wrptoggle}>
<button className={style.btn} onClick={this.toggleShow}>Preview</button>
</div>
{this.state.isShow ?
<div className={style.wrppreviewbox}>
<div className={style.previewbox}>
<Markdown text={this.props.text}/>
</div>
</div>
: null}
</>
)
}
}
Since the above also contains <Markdown/> here's that component:
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({text}) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
}
export default Markdown;
I don't see any complexity in PreviewBox that would cause any rendering delay so I might assume it's the Markdown component that may take some time "working" when it's rerendered since you say "toggle off PreviewBox, there's no lag in when updating title".
Solution
You can use the memo Higher Order Component to decorate the Markdown component and provide a custom areEqual props compare function.
import { memo } from 'react';
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";
const Markdown = ({ text }) => {
return (
<div>
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
children={text}
/>
</div>
);
};
export default memo(Markdown);
By default it will only shallowly compare complex objects in the props
object. If you want control over the comparison, you can also provide
a custom comparison function as the second argument.
const areEqual = (prevProps, nextProps) => {
return prevProps.text === nextProps.text;
};
export default memo(Markdown, areEqual);

How to detect when toggle state has been changed in another component in react

I am researching render props in React. I have a small test project build solely for learning. Favorite component toggles the heart from empty to full when the state of "on
" changes. Toggler component handles the state of on and changes /sets state of on with the toggle function. I'm now on FavoriteText component. What I would like this component to do is when state changes in Favorite component to say a full heart, I want the text in the FavoriteText component to reflect the change with text saying "full heart" or "empty heart". I realize this could've easily been done by including the text in Favorite, but again i am looking to acquire more knowledge on render props in React.
FavoriteText.js
import React, {Component} from 'react'
import Toggler from "./Toggler"
function FavoriteText(props) {
return(
<Toggler defaultOnValue={false} render={
(on)=>(
<h1>{on } ? "Full Heart": "Empty Heart"}</h1>
)
}/>
)
}
export default FavoriteText
Toggler.js
import React, {Component} from "react"
class Toggler extends Component {
state = {
on: this.props.defaultOnValue
}
toggle = (e) => {
this.setState(prevState => {
return {
on: !prevState.on
}
})
}
render() {
return (
<div>{this.props.render(this.state.on, this.toggle)}</div>
)
}
}
export default Toggler
Favorite.js
import React, {Component} from "react"
import Toggler from "./Toggler"
function Favorite(props) {
return (
<Toggler defaultOnValue={false} render={
(on, toggle)=> (
<div>
<h3>Click heart to favorite</h3>
<h1>
<span
onClick={toggle}
>
{on ? "❤️" : "♡"}
</span>
</h1>
</div>
)} />
)
}
export default Favorite

Component render triggered, but DOM not updated

I'm having problems with my first React application.
In practice, I have a hierarchy of components (I'm creating a multimedia film gallery) which, upon clicking on a tab (represented by the Movie component) must show the specific description of the single film (SingleMovieDetails).
The problem is that the DOM is updated only on the first click, then even if the SingleMovieDetails props change, the DOM remains locked on the first rendered movie.
Here's the code i wrote so far...
//Movie component
import React from "react";
import styles from "./Movie.module.scss";
import PropTypes from "prop-types";
class Movie extends React.Component{
constructor(props){
super(props);
this.imgUrl = `http://image.tmdb.org/t/p/w342/${this.props.movie.poster_path}`;
}
render(){
if(!this.props.size)
return <div onClick={this.props.callbackClick(this.props.movie.id)}
name={this.props.movie.id}
className={styles.movieDiv}
style={{backgroundImage: `url(${this.imgUrl})`}}></div>;
return <div onClick={() => this.props.callbackClick(this.props.movie.id)}
name={this.props.movie.id}
className={styles.movieDivBig}
style={{backgroundImage: `url(${this.imgUrl})`}}></div>;
}
}
Movie.propTypes = {
movie: PropTypes.any,
callbackClick: PropTypes.any
};
export default Movie;
SingleMovieDetails.js
import React from "react";
import styles from "./SingleMovieDetails.module.scss";
import Movie from "../Movie";
import SingleMovieDescription from "../SingleMovieDescription";
import MovieCast from "../MovieCast";
import SingleMovieRatings from "../SingleMovieRatings";
class SingleMovieDetails extends React.Component{
constructor(props){
super(props);
console.log(props);
this.state = props;
console.log('constructor', this.state.movie)
}
render(){
console.log('SMD', this.state.movie)
return (
<>
<div className={styles.container}>
<div className={styles.flayer}>
<Movie size={'big'} movie={this.state.movie}/>
</div>
<div className={styles.description}>
<SingleMovieDescription movie={this.state.movie}/>
<MovieCast></MovieCast>
</div>
<div className={styles.ratings}>
<SingleMovieRatings />
</div>
</div>
</>
);
}
}
export default SingleMovieDetails;
MovieCarousel.js
import React from "react";
import PropTypes from "prop-types";
import Movie from "../Movie";
import styles from "./MovieCarousel.module.scss";
import SingleMovieDetails from "../SingleMovieDetails";
class MovieCarousel extends React.Component {
constructor(props) {
super(props);
this.state = [];
this.callbackClickMovie = this.callbackClickMovie.bind(this);
}
callbackClickMovie(id) {
const singleMovieApi = `https://api.themoviedb.org/3/movie/${id}?api_key=b6f2e7712e00a84c50b1172d26c72fe9`;
fetch(singleMovieApi)
.then(function(response) {
return response.json();
})
.then(data => {
this.setState({ selected: data });
});
}
render() {
let details = null;
if (this.state.selected) {
details = <SingleMovieDetails movie={this.state.selected} />;
}
let counter = 6;
let movies = this.props.movies.map(el => {
let element = (
<Movie movie={el} callbackClick={this.callbackClickMovie} />
);
counter--;
if (counter >= 0) return element;
return;
});
let content = (
<>
<h2 className={styles.carouselTitle}>{this.props.title}</h2>
{movies}
{details}
</>
);
return content;
}
}
MovieCarousel.propTypes = {
children: PropTypes.any
};
export default MovieCarousel;
I would be really grateful if someone could help me. I have been on it for two days but I can't really deal with it
This is because in SingleMovieDetails component, you are storing the props values in state and not updating the state on props change. constructor will not get called again so state will always have the initial values.
You have two options to solve the issue:
Directly use the props values instead of storing data in state (preferred way). Like this:
class SingleMovieDetails extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<>
<div className={styles.container}>
<div className={styles.flayer}>
<Movie size={'big'} movie={this.props.movie}/>
</div>
<div className={styles.description}>
<SingleMovieDescription movie={this.props.movie}/>
<MovieCast></MovieCast>
</div>
<div className={styles.ratings}>
<SingleMovieRatings />
</div>
</div>
</>
);
}
}
Use getDerivedStateFromProps, and update the state value on props change.
Same issue with Movie component also, put this line in the render method, otherwise it will always show same image:
const imgUrl = `http://image.tmdb.org/t/p/w342/${this.props.movie.poster_path}`
And use this imgUrl variable.
your Problem is just related to one file: SingleMovieDetails.js
Inside the constructor you´re setting the component state to get initialized with the props (send to the component the first time)
But inside your render() method you are referencing that state again:
<Movie size={'big'} movie={this.state.movie}/>
All in all thats not completely wrong, but you need to do one of two things:
Add a method to update your component state with the nextPropsReceived (Lifecycle Method was called: will receive props, if you are using the latest version you should use: getDerivedStateFromProps)
preferred option: you dont need a state for the movie component, so just use the props inside the render function (this.props.movie)
afterwards you can also delete the constructor, because there is nothing special inside. :)
edit:
So, just to be clear here: Since you´re only setting the state once (the constructor is not called on every lifecycle update) you will always only have the first value saved. Changing props from outside will just trigger render(), but wont start the constructor again ;D

React component re renders to it's initial state automatically

I am making a simple react test application.
What is the application: It shows names of projects and has an option to add a new project. Each project has title and category.
This is how it looks
Problem: When I try to add a new project by entering the title and then clicking on submit button, the new project name appears in the projects for a fraction of seconds and then disappears. The project list gets back to the initial state which is three projects (which are shown below)
This is the code
App.js
import React, { Component } from 'react';
import './App.css';
import Projects from "./Components/Projects"
import AddProjects from './Components/AddProject'
class App extends Component {
constructor(){
super();
this.state = {
projects:[]
}
}
componentWillMount(){
this.setState({projects:[
{
title: "Trigger",
category: "Web App"
},
{
title: "Trigger",
category: "Web App"
},
{
title: "Trigger",
category: "Web App"
}
]})
}
handleAddProject(project){
let projects = this.state.projects;
projects.push(project)
this.setState({projects:projects})
console.log(this.state.projects)
}
render() {
return (
<div className="App">
My Project
<AddProjects addProject={this.handleAddProject.bind(this)}/>
<Projects projects={this.state.projects}/>
</div>
);
}
}
export default App;
Projects.js
import React, { Component } from 'react';
import Projectitem from './ProjectItem'
class Projects extends Component {
render() {
let projectItems
if(this.props.projects){
projectItems = this.props.projects.map(project =>{
return (
<Projectitem project={project}/>
);
})
}
else{
console.log("hello")
}
return (
<div>
This is a list of objects
{projectItems}
</div>
);
}
}
export default Projects
ProjectItem.js
import React, { Component } from 'react';
class ProjectItem extends Component {
render() {
return (
<li>
{this.props.project.title}:{this.props.project.category}
</li>
);
}
}
export default ProjectItem
AddProject.js
import React, { Component } from 'react';
var categories = ["Web dev", "Mobile dev", "websiite"]
class AddProject extends Component {
constructor(){
super();
this.state = {
newProject:{}
}
}
handleSubmit(){
this.setState({newProject:{
title:this.refs.title.value,
category:this.refs.category.value
}}, function () {
this.props.addProject(this.state.newProject)
})
}
render() {
var categoryOptions = categories.map(category=>{
return <option key={category} value={category}>{category}</option>
})
return (
<div>
Add Project <br/>
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>title</label><br/>
<input type="text" ref="title"/>
</div>
<div>
<label>Category</label><br/>
<select ref="category">
{categoryOptions}
</select>
</div>
<input type="submit" value="Submit"/>
</form>
</div>
);
}
}
export default AddProject
Add event.preventDefault() to your handleSubmit() method
handleSubmit(event) {
event.preventDefault();
alert('A name was submitted: ' + this.state.value);
}
Without it your page will refresh (as it does by default when a form is submitted)
You are editting the state directly, that's a big no-no in React.
Change your handler function to something like this:
handleAddProject(project){
this.setState((prevState) => {
return { projects: [...prevState.projects, project] }
})
}
The setState function can be called using a 'callback', that receives the state previous to its call, and lets you modify it. The important part is not mutating the state directly (as your projects.push(project) line is doing).
Other option, that's more like what you are already trying to do, is copying the current state before mutating it:
handleAddProject(project){
let projects = this.state.projects.slice(); // notice the slice here will return
// a copy of 'projects', and then
// you modify it
projects.push(project)
this.setState({projects:projects})
console.log(this.state.projects)
}
Also, keep in mind, setState is called asynchronously, so the console.log(this.state.projects) call may show the old state yet, as setState may not have been called yet.
You are using a Form tag which is for submit information to a web server if you use the input type="submit" it will always reload the page and initial everithig as the first time loaded. If you do as #Galupuf it will work
because the event.preventDefault(); will not allow the browser to reload and it will not initial everyting as first time loaded.
My answer is if you are not sending any data to any sever or working with a server at all change the form tag for div the input type="submit" for<button onClick={()=>this.handleSubmit()}>Send</button>
<div>
<div>
<label>title</label><br/>
<input type="text" ref="title"/>
</div>
<div>
<label>Category</label><br/>
<select ref="category">
{categoryOptions}
</select>
</div>
<button onClick={()=>this.handleSubmit()}>Submit</button>
</div>
other suggestion work the modified value of the state like #cfraser suggest like that you will have immutable data

Conditional List in ReactJS Based On State

I am pretty new to React JS and I am just wondering how I can filter what my component renders based on the state of my prop.
So I have the following component that allows me to select a certain brand and store it as a prop:
var React = require('react');
import Select from 'react-select';
class VehicleSelect extends React.Component {
constructor(props) {
super(props);
this.state = { brandSelect: ""};
}
_onChange(value) {
//console.log(value) - just to see what we recive from <Select />
this.setState({brandSelect: value}, () => {
console.log(this.state.brandSelect);
});
}
render() {
var options = [
{ value: 'Volkswagen', label: 'Volkswagen' },
{ value: 'SEAT', label: 'SEAT' },
{ value: 'SKODA', label: 'SKODA' }
];
return (
<Select
name="form-field-name"
value={this.state.brandSelect}
options={options}
placeholder="Select a brand"
searchable={false}
onChange={this._onChange.bind(this)}
/>
)
}
};
// Export our component
export default VehicleSelect;
This component works as expected however I am having issues when it comes to taking the brandSelect prop and conditionally deciding what my component should render.
Here is my details component:
var React = require('react');
import { If, Then, Else } from 'react-if';
import VehicleSelect from './vehicle-select.js';
// Vehicle Description Component
const VehicleDetail = (props) => {
return (
<If condition={ this.state.brandSelect === props.vehicle.brand.name }>
<div className="col-flex-md-3 col-flex-sm-4 col-flex-xs-6 col-flex-media-query">
<div className="vehicle-container">
<img src={"https://s3-eu-west-1.amazonaws.com/pulman-vw-images/uploads/images/thumbnails/" + props.vehicle.offers[0].image.name} />
<h4 className="vehicle-title">
{props.vehicle.model.name}
</h4>
<div className="row-flex">
<div className="col-flex-xs-12 btn-container">
Learn More
</div>
</div>
</div>
</div>
</If>
);
};
// Export our component
export default VehicleDetail;
As you can see it constructs a HTML container with data. I have also added a conditional statement (react-if on GitHub) to try and render data that matches the option that was selected in my VehicleSelect component however this doesn't seem to work. I get the following error:
Warning: There is an internal error in the React performance measurement code. Did not expect componentDidUpdate timer to start while render timer is still in progress for another instance.
Here is my component that iterates over my VehicleDetail component and supplies data to it:
var React = require('react');
// Vehicle List Componen
import VehicleDetail from './vehicle-detail.js';
// Create our component
const VehicleList = (props) => {
// Just add props.vehicle to access API data instead of static
const RenderedVehicles = props.vehicles.map(vehicle =>
<VehicleDetail key={vehicle.slug} vehicle={vehicle} />
);
return (
<div className="row-flex center-xs">
{RenderedVehicles}
</div>
);
};
// Export our component
export default VehicleList;
So my question is where am I going wrong? As I am new to ReactJS I am unsure how I can render components based on the state that has been selected. All I am trying to do is show data that matches the brandSelect prop in my VehicleSelect component. If the brandSelect prop is equals "" then I would like to render all of the data that is mapped in my VehicleList component.
Thanks
You cannot use this.state.brandSelect in the Details component
Since your details component is setup and brandselect is present in the parent component too

Resources