react component not working properly when rendered multiple times - reactjs

Given below is code of a react component(Newsitem) and I have passed title as props through API. In Newsitem there is a span tag which I need to make hidden when the title passed in Newsitem contains less that 70 characters. But that doesn't happen, what happens is that whenever title has less than 70 characters so the span tag of first newsItem which was rendered get hidden and not of that newsitem to which that title belonged
export class NewsItem extends Component {
state = {
title: true
}
componentDidMount(){
let readTitle = document.getElementById('readTitle')
if(this.props.title.length<70){
readTitle.style.visibility = 'hidden'
console.log('Done....');
}
console.log('componentDidMount() lifecycle');
this.setState({title : !this.state.title})
}
render() {
console.log('Render lifecycle');
let {title , description , imageURL , newsURL} = this.props;
return (
<>
<div>
<div className="card" style={{width: '18rem'}}>
<img src={imageURL} className="card-img-top" alt="..."/>
<div className="card-body">
<h5 className="card-title" style={{display: 'inline'}} id='title'>{title}</h5>
<span id='readTitle'><b>Read More</b></span>
<p className="card-text">{description}</p>
Read More
</div>
</div>
</div>
</>
);
}
}
export default NewsItem;

Further to my comment, I would probably use a stateless functional component, and the React.useRef hook and do something like this:
import React, {useRef, useEffect} from 'react';
const NewsItem = ({title , description , imageURL , newsURL}) => {
const readTitle = useRef(null);
useEffect(()=>{
if(title.length<70){
readTitle.current.style.visibility = 'hidden'
console.log('Done....');
}
}, [title])
return (
<div>
<div className="card" style={{width: '18rem'}}>
<img src={imageURL} className="card-img-top" alt="..."/>
<div className="card-body">
<h5 className="card-title" style={{display: 'inline'}} id='title'>{title}</h5>
<span ref={readTitle}><b>Read More</b></span>
<p className="card-text">{description}</p>
Read More
</div>
</div>
</div>
);
};
export default NewsItem;
Should work as expected, and is considerably more concise :)

There is a very important rule in react and it is to not use js dom functions in the middle of components. If you use them you can create conflicts between components and states. You can instead use React refs in this case.

Related

Why is props sent from parent component become object in child component?

I'am implementing a language translation system for my React app and I'm stuck on displaying the languages flags as Modal when the user click on the tranlation icon, which should display the list of languages to tranlate to. I have a LanguageModal.jsx child component which is a Modal triggered from NavigationBar.jsx parent component. These are the two components:
// Languagemodal.jsx
import React from 'react';
import './languageModal.css';
import en from '../../assets/flags/US.png';
import de from '../../assets/flags/DE.png';
import fr from '../../assets/flags/FR.png';
const languageModal = (show) => (show
? (
<div className="Modal">
<div className="Modal-content">
<div className="Modal-body">
<div><img src={en} alt="english" /></div>
<div><img src={de} alt="deutsch" /></div>
<div><img src={fr} alt="francais" /></div>
</div>
</div>
</div>
) : (null));
export default languageModal;
// NavigationBar.jsx
import React, { useState, useEffect } from 'react';
import { FaGlobe } from 'react-icons/fa';
import logo from '../../assets/logodidi.png';
import Modal from '../LanguageModal/languageModal.jsx';
import './navigationBar.css';
const NavigationBar = () => {
const [toggleBar, setToggleBar] = useState(false);
const [show, setShow] = useState(false);
useEffect(() => {
setShow(show);
}, [show]);
return (
<div className="navbar">
<div id="max-width">
<div className="navbar-items">
<div className="navbar-items_container">
<p>home</p>
<p>Usage</p>
<p>Categories</p>
<p>Blogs</p>
</div>
</div>
<div className="navbar-sign">
<p><FaGlobe color="#2b74b7" onClick={() => (setShow(true))} /></p>
<languageModal show={show} />
<button type="button">Get started</button>
</div>
</div>
</div>
);
};
export default NavigationBar;
The expected behavior is to display only when the user cliks on the FaGlob icon. The problem is that the Modal is automatically displayed without a click. so I set the variable "show" with React hook useState and useEffect to manage show/hide the Modal but not to avail, the Modal is always being displayed anyway. When I check the "show" value with docuemnt.write(show) I saw that it is "false" in the parent component but it gives [object object] in the languageModal child component. When I change the "show" initial value it doesn't take any effect inside the child languageModal component unless I manualy change it inside it by changing the
const languageModal = (show) => (show
? ( ... )
to
const languageModal = (show) => (!show
? (...)
And that is the weird point. Why the child component doesn't receive the value passed to it? Where is it taking the default value [object object] from, since "show" isn't an object but a boolean ? How to solve it then. Your help is very appreciated. I can't go forth whithout your help.
You are destructuring show incorrectly. Update it as:
const languageModal = ({show}) => (show
? (
<div className="Modal">
<div className="Modal-content">
<div className="Modal-body">
<div><img src={en} alt="english" /></div>
<div><img src={de} alt="deutsch" /></div>
<div><img src={fr} alt="francais" /></div>
</div>
</div>
</div>
) : (null));
export default languageModal;
When you write show like that, show will be props. That is why it is an object. If you want to extract a particular property, you can either do props.show or use destructuring in the functional component like this {show}
const languageModal = ({show}) => (show
? (
<div className="Modal">
<div className="Modal-content">
<div className="Modal-body">
<div><img src={en} alt="english" /></div>
<div><img src={de} alt="deutsch" /></div>
<div><img src={fr} alt="francais" /></div>
</div>
</div>
</div>
) : (null));

Working with images local files in Gatsby.js

I am trying to render a headshot for each artist displayed on the page. The artist data comes from a Json file, and the images are located in images/artists/headshots. I am using the regular img tag, but for some reason nothing is displaying on the page. Any help any one can give would be greatly appreciated.
import React from 'react'
import PropTypes from 'prop-types'
import { StaticImage } from 'gatsby-plugin-image'
import { useStyles } from './styles'
const ArtistsPage = ({ data }) => {
const classes = useStyles()
return (
<section>
<article className={classes.artistsBackground}>
<div className={classes.heroLayoutContainer}>
<h3 className={classes.title}>MEET THE ARTISTS</h3>
<StaticImage
className={classes.heroImage}
src='../../images/artists/hero-images/hero-image-artists.jpg'
alt='hero-image-artists'
placeholder='blurred'
/>
</div>
</article>
<article className={classes.artistsContainer}>
<div className={classes.flexContainer}>
{data.allArtistsJson.edges
.map(({ node }, idx) => {
const artist = `${node.firstName}+${node.lastName}`
.split('+')
.join(' ')
return (
<div className={classes.flexItem} key={idx}>
<div>
<img
src={`../../images/artists/headshots/${artist} Headshot.jpg`}
alt='artist-headshot'
/>
</div>
<div className={classes.artistCardName}>
{`${node.firstName} ${node.lastName}`.toUpperCase()}
</div>
<div className={classes.artistCardText}>{node.city}</div>
<div className={classes.artistCardText}>
{node.currentTeam}
</div>
</div>
)
})}
</div>
</article>
</section>
)
}
export default ArtistsPage
My image files are set up as:
FirstName LastName Headshots.jpg
I think your issue may comes from the white spaces in the naming. Your code looks good at first sight so try renaming your images with underscore or in camelCase:
<img
src={`../../images/artists/headshots/${artist}_Headshot.jpg`}
alt='artist-headshot'
/>
After much research and the nice people on Gatsby discord I found the answer to be… in a scenario like this I needed to add require().default.
Ex:
<img
src={require(../../images/artists/headshots/${artist} Headshot.jpg).default}
alt='artist-headshot'
/>

Caching in Gatsby JS

I have this Gatsby app where when I visit home page everything loads at first including the Testimonials section. However, when I visit another page, for instance the Blog list page and then click on the link back to homepage the data on Testimonials component won't load and you will see a blank area of the app where the Testimonials section is placed. In order to see the Testimonials list, you will have the refresh the page again.
For the record the data on my Testimonials component are being pulled out on Strapi JS that is deployed on Heroku.
So far I got this on my index.js:
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/Layout"
import Header from "../components/Header"
import Testimonials from '../components/Testimonials'
import Blogs from '../components/Blogs'
import SEO from '../components/SEO'
export default function Home({ data }) {
const {
allStrapiBlogs: { nodes:blogs },
} = data
return (
<>
<SEO />
<div className="main-container">
<Layout>
<Header />
<Testimonials title="Testimonials" />
<Blogs title="Latest Blogs" blogs={blogs} showAllBlogLinks/>
<Map />
</Layout>
</div>
</>
)
}
export const query = graphql`
{
allStrapiBlogs(filter: {featured: {eq: true}}, sort: {fields: published_date, order: DESC}, limit: 6) {
nodes {
id
title
content
slug
published_date(formatString: "MMMM DD, YYYY")
featured_image {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
And then on my Testimonials.js component:
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
import Image from "gatsby-image"
import { FaStar } from "react-icons/fa"
import Title from './Title'
const query = graphql`
{
allStrapiTestimonials {
nodes {
id
name
cite
text
photo {
childImageSharp {
fluid{
...GatsbyImageSharpFluid_withWebp
}
}
}
}
}
}
`
const Testimonials = ({ title }) => {
const data = useStaticQuery(query)
const { allStrapiTestimonials: { nodes:testimonials } } = data
return (
<div className="testimonial-section section-padding" id="testimonial" data-aos="zoom-in">
<div className="container">
<div className="row">
<div className="col-lg-12">
<div className="section-title-two center">
<Title title={title} />
</div>
</div>
</div>
<div className="testimonial-wrap row">
<div className="testimonial-slider owl-carousel">
{ testimonials.map(item => {
const { id, name, cite, text, photo } = item
return(
<div className="col-xl-8 col-lg-10 col-12 ml-auto mr-auto" key={id}>
<div className="testimonial-item mt-40">
<div className="testimonial_img">
<Image fluid={photo.childImageSharp.fluid} alt={name} style={{ position: "absolute", overflow: "visible", display: "block", width: "211px", height: "207px" }} />
</div>
<div className="testimonial_content xs-mt-40">
<div className="testimonial_content_item mb-30">
<div className="testimonial_content__pro">
<h4 className="mb-10">{ name }</h4>
<p>{ cite }</p>
</div>
<ul className="d-none d-sm-inline-block">
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
<li><FaStar></FaStar></li>
</ul>
</div>
<div>
<p>{ text } </p>
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</div>
</div>
)
}
export default Testimonials
Any idea what's causing this error? How can I fix it?
I've faced a similar issue a few months ago and the fix depends strictly on the implementation and your code. Basically, what is happening is that React's doesn't understand that he needs to rehydrate some components because pointing to some global objects as window or document of the DOM, outside the React's ecosystem (without using states) may block that rehydration, in your cause, because of jQuery.
All the possible solutions that bypass this issue will be patches (like trying to add the cache). The ideal solution would avoid using jQuery, which points directly to the DOM, with React, which manipulates the virtual DOM (vDOM).
There's nothing wrong with the code you've shared, however, based on some other questions that you did, you are using jQuery and using the window object, which prevents the rehydration of some React components. You should get rid of jQuery or using some React-based approach. Something like this should do the trick to force a loader across the whole site:
const Layout = ({ children }) => {
const [loader, setLoader]=useState(true);
useEffect(()=>{
setTimeout(()=> {
setLoader(false)
}, 400)
}, [])
return <section className={`site__wrapper`}>
<Header />
<main>{loader ? <Preloader/> : children}</main>
</section>;
};

looping json data in Class Component

This is my index.js where I try to refer SampleApp
import React, { Component } from "react";
import { render } from "react-dom";
import './index.css';
import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect
} from "react-router-dom";
import SampleApp from "./pages/SampleApp";
import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
isUserAuthenticated: true
};
}
render() {
return (
<Router>
<Switch>
<Route
exact
path="/"
render={() => {
return (
<Redirect to="/SampleApp" />
)
}}
/>
<Route exact path="/SampleApp" component={SampleApp} />
</Switch>
</Router>
);
}
}
render(<App />, document.getElementById("root"));
This is my SampleApp.js file. here I'm importing the Cards component from Cards.jsx
import React from 'react';
import '../../src/App.css';
import Cards from '../cards/cards';
const SampleApp = props => (
<React.Fragment>
<div className="App">
<div>
<div className="header">
<div className="header_fonts">
Sample Application
</div>
</div>
<div>
<div className="content_header_fonts">
This is sample app
</div>
<div className="content_fonts">
Sample app to deomntrate ideas.
</div>
<Cards></Cards>
</div>
</div>
</div>
</React.Fragment>
)
export default SampleApp;
this is my Cards.jsx file.
here I'm importing Card component and json data
import React, { Component } from "react";
import Card from './cardUI';
import CardData from '../source/data.json';
class Cards extends Component {
render() {
return
(
<div className="container-fluid d-flex justify-content-center">
<div className="row">
{
CardData.map((
{title, desc, icon, intro,developer_guide,api_ref }, id) =>
(
<div className="col-md-4">
<Card
title={title}
desc={desc}
intro={intro}
developer_guide={developer_guide}
api_ref={api_ref}/>
</div>
))
}
</div>
</div>
);
}
}
export default Cards;
this is a sample of my JSON file
[
{
"id" : 7,
"title" : "Melon Munchee",
"icon" : "https://cdn.onlinewebfonts.com/svg/img_393496.png",
"desc" : "If you are an Avatar fan, then this api is for you. Here you can find everything from Episodes to Characters.",
"intro": "intro_7",
"developer_guide": "d_link7",
"api_ref": "api_link7"
},
{
"id" : 8,
"title" : "Browns Barns",
"icon" : "https://cdn.onlinewebfonts.com/svg/img_386567.png",
"desc" : "Baseball fans? Computer nerds? Now, in one place, you have baseball data and an api to access it. Have fun!.",
"intro": "intro_8",
"developer_guide": "d_link8",
"api_ref": "api_link8"
}
]
Card.jsx file
This is how implemented the Card component
import React from 'react';
import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
import * as Icon from '../../node_modules/react-bootstrap-icons';
import './card-style.css';
const Card = props =>{
return(
<div className="card text-center">
<div className="card-body text-dark">
<Icon.Alarm></Icon.Alarm>
<h4 className="card-title">
{props.title}
</h4>
<p className="card-text text-secondary">
{props.desc}
</p>
<ul class="list-group">
<li class="list-group-item">{props.intro}</li>
<li class="list-group-item">{props.developer_guide}</li>
<li class="list-group-item">{props.api_ref}</li>
</ul>
</div>
</div>
)
}
export default Card;
but I'm getting an error as following
Error: Cards(...): Nothing was returned from render. This usually
means a return statement is missing. Or, to render nothing, return null.
19 stack frames were collapsed. Module.
src/index.js:44 41 | } 42 | } 43 |
44 | render(, document.getElementById("root"));
I am actually going to take a guess here and say that your specific error is caused by the new line after your return statement. So remove it to make it look like this return ( and it should work... or at least that error should go away.
Check out this sandbox: https://codesandbox.io/s/xenodochial-fog-y8pk2?file=/src/App.js
just go ahead and add a new line after the return and see your exact error.
It's just a typo mistake. When you use line terminator next to the return statement, JS adds semicolon automatically and that will be the end of function execution and returns undefined. That's why your Cards component is not able to find the JSX because Cards render returns undefined.
As per MDN docs.
The return statement is affected by automatic semicolon insertion (ASI). No line terminator is allowed between the return keyword and the expression.
To fix this, update Cards render function with this
class Cards extends Component {
render() {
return ( // was the issue earlier
<div className="container-fluid d-flex justify-content-center">
<div className="row">
{
CardData.map(({ title, desc, icon, intro,developer_guide,api_ref }, index) => (
<div className="col-md-4" key={title + index}>
<Card
title={title}
desc={desc}
intro={intro}
developer_guide={developer_guide}
api_ref={api_ref} />
</div>
)
)
}
</div>
</div>
);
}
}

How to trigger a function from one component to another component in React.js?

I'am creating React.js Weather project. Currently working on toggle switch which converts celcius to fahrenheit. The celcius count is created in one component whereas toggle button is created in another component. When the toggle button is clicked it must trigger the count and display it. It works fine when both are created in one component, but, I want to trigger the function from another component. How could I do it? Below is the code for reference
CelToFahr.js (Here the count is displayed)
import React, { Component } from 'react'
import CountUp from 'react-countup';
class CeltoFahr extends Component {
state = {
celOn: true
}
render() {
return (
<React.Fragment>
{/* Code for celcius to farenheit */}
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<CountUp
start={!this.state.celOn ? this.props.temp.cel : this.props.temp.fahr}
end={this.state.celOn ? this.props.temp.cel : this.props.temp.fahr}
duration={2}
>
{({ countUpRef, start}) => (
<h1 ref={countUpRef}></h1>
)}
</CountUp>
</div>
</div>
</div>
</div>
{/*End of Code for celcius to farenheit */}
</React.Fragment>
)
}
}
export default CeltoFahr
CelToFahrBtn (Here the toggle button is created)
import React, { Component } from 'react'
import CelToFahr from './CeltoFahr'
class CelToFahrBtn extends Component {
state = {
celOn: true
}
switchCel = () => {
this.setState({ celOn: !this.state.celOn })
}
render = (props) => {
return (
<div className="button" style={{display: 'inline-block'}}>
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<div onClick={this.switchCel} className="CelSwitchWrap">
<div className={"CelSwitch" + (this.state.celOn ? "" : " transition")}>
<h3>C°</h3>
<h3>F°</h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default CelToFahrBtn
Here when I click on switchCel it must trigger the celcius to fahrenheit value and vice-versa. How to do it? Any suggestions highly appreciated. Thanks in advance
I would have the celToFahr be the parent component of the celToFahrBtn and then pass the function you want to invoke via props
<CellToFahrBtn callback={yourfunction}/>
What else could you do is having a common parent for these to components where you would again do the execution via props and callbacks
The 3rd option would be having a global state which would carry the function like Redux or Reacts own Context. There again you would get the desired function via props and you would execute it whenever you like. This is the best option if your components are completely separated in both the UI and in source hierarchically, but I don't think this is the case in this case.
https://reactjs.org/docs/context.html
These are pretty much all the options you have
To achieve this you'd need to lift your state up and then pass the state and handlers to the needed components as props.
CeltoFahr & CelToFahrBtn would then become stateless components and would rely on the props that are passed down from TemperatureController
class TemperatureController extends Component {
state = {
celOn: true
}
switchCel = () => {
this.setState({ celOn: !this.state.celOn })
}
render () {
return (
<React.Fragment>
<CeltoFahr celOn={this.state.celOn} switchCel={this.state.switchCel} />
<CelToFahrBtn celOn={this.state.celOn} switchCel={this.state.switchCel}/>
</React.Fragment>
)
}
}
It's probably better explained on the React Docs https://reactjs.org/docs/lifting-state-up.html
See this more simplified example:
import React, {useState} from 'react';
const Display = ({}) => {
const [count, setCount] = useState(0);
return <div>
<span>{count}</span>
<Button countUp={() => setCount(count +1)}></Button>
</div>
}
const Button = ({countUp}) => {
return <button>Count up</button>
}
It's always possible, to just pass down functions from parent components. See Extracting Components for more information.
It's also pretty well described in the "Thinking in React" guidline. Specifically Part 4 and Part 5.
In React you should always try to keep components as dumb as possible. I always start with a functional component instead of a class component (read here why you should).
So therefore I'd turn the button into a function:
import React from 'react';
import CelToFahr from './CeltoFahr';
function CelToFahrBtn(props) {
return (
<div className="button" style={{ display: 'inline-block' }}>
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<div onClick={() => props.switchCel()} className="CelSwitchWrap">
<div
className={'CelSwitch' + (props.celOn ? '' : ' transition')}
>
<h3>C°</h3>
<h3>F°</h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default CelToFahrBtn;
And you should put the logic in the parent component:
import React, { Component } from 'react';
import CountUp from 'react-countup';
import CelToFahrBtn from './CelToFahrBtn';
class CeltoFahr extends Component {
state = {
celOn: true
};
switchCel = () => {
this.setState({ celOn: !this.state.celOn });
};
render() {
return (
<>
<div className="weather">
<div className="figures">
<div className="figuresWrap2">
<div className="mainFigureWrap">
<CelToFahrBtn switchCel={this.switchCel} celOn={celOn} />
</div>
</div>
</div>
</div>
</>
);
}
}

Resources