React div style not updating on render - reactjs

I'm trying to update the background color of a div when an array in my context is populated. On the initial render the background is being set as I would expect, however when the context is updated and a new value is pushed into the array, I can see the length increasing in the tags, however the background color set in the style doesn't seem to update.
Code below:
import React, { Component } from 'react';
import { Consumer } from '../../context';
import AlertWindow from './AlertWindow';
class AlertTab extends Component {
constructor(props){
super(props);
this.state = {
type: props.type,
active_color: props.active_color
}
}
render() {
return (
<Consumer>
{value => {
const { geofence_alarms } = value;
const { type, active_color } = this.state;
return (
<div>
<div className='alert-tab' style={{background: (geofence_alarms.length > 0 ? active_color : '#8E8E8E')}}>
<h6>{type}</h6>
<h6 style={{fontSize:'22pt', float: 'right'}}>{geofence_alarms.length}</h6>
</div>
<AlertWindow />
</div>
)
}}
</Consumer>
)
}
}
export default AlertTab;
You can see from the below images that the length of the geofence_alerts array does indeed increase:
I'm assuming this has something to do with the way styles are being loaded in React, but does anyone know the proper way I could achieve this?
Thanks in advance!

Simply put, don't use this.state for props. When properties update, a rerender will happen, but the component will not be reinitialized/remounted. Since you copy over your props to the state in the constructor, this will not be called, and your state will contain the old props that were passed in initially when the component was mounted. In this case, you can pull type and active_color straight from this.props:
import React, { Component } from 'react';
import { Consumer } from '../../context';
import AlertWindow from './AlertWindow';
class AlertTab extends Component {
render() {
return (
<Consumer>
{value => {
const { geofence_alarms } = value;
const { type, active_color } = this.props;
return (
<div>
<div className='alert-tab' style={{background: (geofence_alarms.length > 0 ? active_color : '#8E8E8E')}}>
<h6>{type}</h6>
<h6 style={{fontSize:'22pt', float: 'right'}}>{geofence_alarms.length}</h6>
</div>
<AlertWindow />
</div>
)
}}
</Consumer>
)
}
}
export default AlertTab;

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

Display element based on event fired and props passed in

I am trying, to manipulate another element, by, passing props directly to it, and then have it display itself. If I pass true/false.
Live running code:
https://codesandbox.io/s/keen-dan-rt0kj
I don't know if it's possible to have a system of objects, and based on an event, tell a parent to display a child.
App.js
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
state = {
display: false
};
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}
./components/Content.js:
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
display: props.display
};
}
render() {
const { display } = this.state;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Goal:
I want to based on a state, and based on fired event, display an element that already in store of root.
EDIT: I am aware that, this exists and can be used: import PropTypes from 'prop-types', however, I am not sure this is good practice, since it requires some parent or some other component to implement the props.
JUST Tried:
App:
<Content display={this.state.display} content={"Hello World"} />
Content:
<h3>{this.state.content}</h3>
It seems the passed in text, stored in Content state = {content: props.content} does get displayed, wheres, the boolean value does not work directly. Is there something wrong with sending in a bool ?
try this in your Content Component
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
};
}
render() {
return (
<>
{this.props.display?(
<div
id="mydiv"
className="mydiv"
>
<h3>A simple div</h3>
</div>
):null}
</>
);
}
}
The reason this may not be working is because you are initiating the state in a way that does not connect the display props after the component is initialized. This means that after the Content component is "constructed", the state of the Content and it's parent are not linked. This is because the constructor() function is only run once to initialize the state.
The best option you have is to not use the internal state of the Content component. Rather than initializing state with the display prop, just use the display prop in your render function.
Trying something like this might work
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super(props);
}
render() {
const { display } = this.props;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Also I would reccommend using state in the root:
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
constructor(props) {
super();
state = {
display: false
};
}
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}

React JSX with component in variable does not sync props

The Scegli component is assigned to the App comp variable and then rendered in App render as a variable. However, the props assigned don't work: if I type in the input box, the value is frozen.
What am I doing wrong?
If I just move the <Scegli>...</Scegli> directly into the render (without assigning to a variable) it works as expected.
App.js
import React, { Component } from 'react';
import './App.css';
import Scegli from './components/Scegli';
class App extends Component {
constructor() {
super();
this.state = {
valore: 'Single'
}
this.comp = <Scegli value={this.state.valore} handleChange={this.setValoreHandler} />;
}
setValoreHandler = e => {
this.setState({
valore: e.target.value
});
}
render() {
return (
<div>
{this.comp}
</div>
);
}
}
export default App;
Scegli.js
import React, { Component } from 'react';
class Scegli extends Component {
render() {
return (
<div>
<input value={this.props.value} onChange={this.props.handleChange} />
Valore scelto: {this.props.value}
</div>
);
}
}
export default Scegli;
this.comp is declared once, at the component's mount and stays in that state. You are not updating/re-rendering it anywhere, that's why it remains unchanged.
You could either:
move the JSX component directly to render:
<div>
<Scegli value={this.state.valore} handleChange={this.setValoreHandler} />
</div>
or
update the class variable with every input change (not recommended though):
setValoreHandler = (e) => {
this.comp = <Scegli value={e.target.value} handleChange={this.setValoreHandler} />;
this.forceUpdate();
}
(I could be wrong) but when you define this.comp in the constructor() it is only loaded once with the default state. The constructor() is not called on re-render (similar to componentWillMount()). So that is why it is frozen as the updated state is never sent this.comp
Instead of this.comp in render do
return (
<div>
<Scegli value={this.state.valore} handleChange={this.setValoreHandler}/>
</div>
);

Creating a new room (including inputs and button) and only template shows without the values inside (rcc)

At the button click Create I want to display the room with the content (the new values ​​that holds by the objects in the array - the value I wrote inside the inputs) but fro some reason it's not working and I can't solve it, the problem is that only the template that shows the titles Room and Type are shown without the values inside each of them
Thanks to the helpers!
App.js
import React, { Component } from 'react'
import './App.css';
import Addroom from './components/Addroom.js'
import Room from './components/Room.js'
import 'bootstrap/dist/css/bootstrap.css';
export default class App extends Component {
state={roomsList:[{room:'',type:''}]
}
create=(r,t)=> {
this.setState({roomsList:[...this.state.roomsList,{room:r,type:t}]})
}
render() {
return (
<div>
<h1>My Smart House</h1>
{this.state.roomsList.map((element)=>{
return <Room r={element.room} t={element.type} />
})}
<Addroom add={this.create}/>
</div>
)
}
}
Addroom.js
import React, { Component } from 'react'
export default class Addroom extends Component {
constructor(props) {
super(props)
}
addRoomName=(e)=> {
this.setState({room:e.target.value})
}
addType=(e)=> {
this.setState({type:e.target.value})
}
createRoom=()=> {
this.props.add(this.state.room,this.state.type);
}
render() {
return (
<div>
<input onChange={this.addRoomName} placeholder='Name Your Room'/><br/>
<input onChange={this.addType} placeholder='Whats The Room Type?'/><br/>
<button onClick={this.createRoom}>Create</button>
</div>
)
}
}
Room.js
import React, { Component } from 'react'
export default class Room extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div>
<h1>Room: {this.props.room} </h1>
<h3>Type: {this.props.type} </h3>
</div>
)
}
}
I solved the error, it was a syntax mistake, so what i did, I just asked to get the inside value from my objects in the Room.js components, So it looked like that before:
render() {
return (
<div>
<h1>Room: {this.props.room} </h1>
<h3>Color: {this.props.type} </h3>
</div>
)
}
}
and now I just fixed the syntax to make App.js component understand that I want to display the values inside the objects when I'm creating a new room with my button, because now r and t are represent the values of the variables..
render() {
return (
<div>
<h1>Room: {this.props.r} </h1>
<h3>Color: {this.props.t} </h3>
</div>
)
}
}
This is a very small mistake that is easy to understand, so it is always important to go through your code slowly and safely! Hope it will help some f.e devs in the future..

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

Resources