react conditional rendering with design pattern - reactjs

My question is, what would be the best way to set up the conditional rendering in this code to keep the code scalable and maintainable? i mean in a render function. i would like in future when i add new component,i do not want to touch the old code to add the new component. any ideas? i already know about
{this.state.state1?Component/>:null}
import * as React from "react";
import LoginForm from "./forms/LoginForm";
import MenuForm from "./forms/MenuForm";
import HomeForm from "./forms/HomeForm";
import TermsForm from "./forms/TermsForm";
import IdentificationResultForm from "./forms/IdentificationResultForm";
import CompanyForm from "./forms/CompanyForm";
import handleAppDateInputFromClient from "../api/azureController.js";
import ListViewForm from "./forms/ListViewForm";
import PartDetails from "./forms/PartDetails";
import ThemeForm from "./forms/ThemeForm";
export default class TemplateController extends React.Component {
constructor(props) {
super(props);
this.state = {
flowComponentNumber: 0,
};
this.config = {};
this.translation = {};
}
handelSubmit = (companyName) => {
// call azure controller with companyName and config
handleAppDateInputFromClient(companyName, this.config);
console.log(companyName, this.config);
this.handleFlowComponent();
};
handleLanguage = (key, value) => {
this.translation[key] = value;
};
handleChildSubmit = (key, value) => {
this.config[key] = value;
console.log(this.config);
this.handleFlowComponent();
};
handleFlowComponent = () => {
this.setState((prevState) => ({
flowComponentNumber: prevState.flowComponentNumber + 1,
}));
};
render() {
let component;
// let next = <Button variant="primary" type="button" onClick={this.handleFlowComponent}></Button>
if (this.state.flowComponentNumber === 0) {
component = <LoginForm now={10} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 1) {
component = <HomeForm now={20} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 2) {
component = <TermsForm now={30} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 3) {
component = <IdentificationResultForm now={40} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 4) {
component = <MenuForm now={50} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 5) {
component = (
<ListViewForm
now={60}
handler={this.handleChildSubmit}
languageHandler={this.handleLanguage}
/>
);
} else if (this.state.flowComponentNumber == 6) {
component = (
<PartDetails
now={70}
handler={this.handleChildSubmit}
languageHandler={this.handleLanguage}
/>
);
} else if (this.state.flowComponentNumber === 7) {
component = <ThemeForm now={80} handler={this.handleChildSubmit} />;
} else if (this.state.flowComponentNumber === 8) {
component = <CompanyForm now={90} handler={this.handelSubmit} />;
} else if (this.state.flowComponentNumber === 9) {
component = <h1 style={{ color: "greenyellow" }}> Your submit is successful </h1>;
}
return <>{component}</>;
}
}

Related

why useEffect run twice in react, how to prevent this issue?

right now i have 2 components, my parent component is Game and from that it calls the child component, GameInner, from GameInner component i have define the useEffect, but it calls 2 times, i don't know why. this is my useEffect function, also i have define both component code here,
React.useEffect(() => {
if(!isCalled.current) {
isCalled.current = true;
if(started) {
console.log('i fire once')
if(isCalled.current) {
getUpdate();
}
}
}
}, []);
Game.tsx
import {Redirect, RouteComponentProps, withRouter} from "react-router";
import React, { useEffect, useState } from "react";
import {GameDataStore, GameDataStorePayload} from "../../Global/DataStore/GameDataStore";
import {UserData, UserDataStore} from "../../Global/DataStore/UserDataStore";
import Helmet from "react-helmet";
import {Dialog, DialogContent, Typography} from "#material-ui/core";
import {ContainerProgress} from "../../UI/ContainerProgress";
import {LoadingButton} from "../../UI/LoadingButton";
import {Support} from "./Components/Gameplay/Support";
import {GameChatFab} from "./Components/Chat/GameChatFab";
import {ChatSidebar} from "./Components/Chat/ChatSidebar";
import {GameInner} from "./Components/Gameplay/GameInner";
import {SocketDataStore, SocketDataStorePayload} from "../../Global/DataStore/SocketDataStore";
import moment from "moment";
import {getTrueRoundsToWin} from "../../Global/Utils/GameUtils";
import {ClientGameItem} from "../../Global/Platform/Contract";
import {PlayerJoinApproval} from "#Areas/Game/Components/Gameplay/PlayerJoinApproval";
interface IGameParams
{
id: string;
}
interface IGameState
{
socketData: SocketDataStorePayload;
gameData: GameDataStorePayload;
userData: UserData;
restartLoading: boolean;
restartDelayed: boolean;
showSupport: boolean;
chatDrawerOpen: boolean;
}
class Game extends React.Component<RouteComponentProps<IGameParams>, IGameState>
{
private supportDelayTimeout = 0;
constructor(props: RouteComponentProps<IGameParams>)
{
super(props);
this.state = {
socketData: SocketDataStore.state,
gameData: GameDataStore.state,
userData: UserDataStore.state,
restartLoading: false,
restartDelayed: true,
showSupport: false,
chatDrawerOpen: true
};
}
public componentDidMount(): void
{
GameDataStore.hydrate(this.props.match.params.id);
SocketDataStore.listen(data => this.setState({
socketData: data
}));
GameDataStore.listen(data => this.setState({
gameData: data
}));
UserDataStore.listen(data => this.setState({
userData: data
}));
}
private getWinnerFromState(state: IGameState)
{
const {
players,
settings
} = state.gameData.game ?? {};
const playerGuids = Object.keys(players ?? {});
const roundsToWin = getTrueRoundsToWin(state.gameData.game as ClientGameItem);
const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);
return winnerGuid;
}
public componentDidUpdate(prevProps: Readonly<RouteComponentProps<IGameParams>>, prevState: Readonly<IGameState>, snapshot?: any): void
{
const hadWinner = this.getWinnerFromState(prevState);
const hasWinner = this.getWinnerFromState(this.state);
if (!hadWinner && hasWinner && this.supportDelayTimeout === 0)
{
this.supportDelayTimeout = window.setTimeout(() =>
{
this.setState({
restartDelayed: true,
showSupport: true
});
setTimeout(() => this.setState({
restartDelayed: false
}), 5000);
}, 2000);
}
}
private restartClick = (playerGuid: string) =>
{
this.setState({
restartLoading: true
});
GameDataStore.restart(playerGuid)
.finally(() => this.setState({
restartLoading: false
}));
};
public render()
{
const {
id,
} = this.props.match.params;
if (!id)
{
return <Redirect to={"/"}/>;
}
const {
dateCreated,
ownerGuid,
spectators,
pendingPlayers,
players,
settings,
} = this.state.gameData.game ?? {};
if (!this.state.gameData.game || !this.state.gameData.loaded || !this.state.socketData.hasConnection)
{
return <ContainerProgress/>;
}
const {
playerGuid
} = this.state.userData;
const owner = players?.[ownerGuid ?? ""];
const amInGame = playerGuid in (players ?? {});
const amSpectating = playerGuid in {...(spectators ?? {}), ...(pendingPlayers ?? {})};
const title = `${unescape(owner?.nickname ?? "")}'s game`;
const playerGuids = Object.keys(players ?? {});
const roundsToWin = getTrueRoundsToWin(this.state.gameData.game as ClientGameItem);
const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);
const canChat = (amInGame || amSpectating) && moment(dateCreated).isAfter(moment(new Date(1589260798170)));
return (
<>
<Helmet>
<title>{title}</title>
</Helmet>
<PlayerJoinApproval/>
<GameInner gameId={id} />
{winnerGuid && (
<Dialog open={this.state.showSupport} onClose={() => this.setState({showSupport: false})}>
<DialogContent style={{padding: "2rem"}}>
<Typography variant={"h6"} style={{textAlign: "center"}}>
Game over! {unescape(players?.[winnerGuid].nickname ?? "")} is the winner.
</Typography>
<Support/>
{playerGuid === ownerGuid && (
<div style={{
marginTop: "7rem",
textAlign: "center"
}}>
<LoadingButton loading={this.state.restartLoading || this.state.restartDelayed} variant={"contained"} color={"secondary"} onClick={() => this.restartClick(playerGuid)}>
Restart this game?
</LoadingButton>
</div>
)}
</DialogContent>
</Dialog>
)}
{canChat && (
<>
<GameChatFab showChat={amInGame || amSpectating}/>
<ChatSidebar />
</>
)}
</>
);
}
};
export default withRouter(Game);
GameInner.tsx
import { Alert } from "#material-ui/lab";
import { Typography, useMediaQuery } from "#material-ui/core";
import { ShowWinner } from "./ShowWinner";
import { ErrorBoundary } from "../../../../App/ErrorBoundary";
import { GamePlayWhite } from "../../GamePlayWhite";
import { GamePlayBlack } from "../../GamePlayBlack";
import { GamePlaySpectate } from "../../GamePlaySpectate";
import React, { useEffect, useState } from "react";
import { useDataStore } from "../../../../Global/Utils/HookUtils";
import { GameDataStore } from "../../../../Global/DataStore/GameDataStore";
import { UserDataStore } from "../../../../Global/DataStore/UserDataStore";
import { IntervalDataStore } from "../../../../Global/DataStore/IntervalDataStore";
import GameStart from "../../GameStart";
import GameJoin from "../../GameJoin";
import moment from "moment";
import { ChatDataStore } from "../../../../Global/DataStore/ChatDataStore";
import { useHistory, useParams } from "react-router";
import { SiteRoutes } from "../../../../Global/Routes/Routes";
import { getTrueRoundsToWin } from "../../../../Global/Utils/GameUtils";
import { ClientGameItem } from "../../../../Global/Platform/Contract";
import { CurriedFunction1 } from "lodash";
interface Props {
gameId: string;
}
export const GameInner: React.FC<Props> = (
{
gameId,
}
) => {
const gameData = useDataStore(GameDataStore);
const userData = useDataStore(UserDataStore);
const chatData = useDataStore(ChatDataStore);
const params = useParams<{ throwaway?: string }>();
const history = useHistory();
const [updateShowTimer, setUpdateShowTimer] = React.useState('02:00');
//const [isCalled, setIsCalled] = React.useState<any>('0');
const [intervalData, setIntervalData] = useState(null as NodeJS.Timeout | null);
let setSeconds = 30;
const isGameStarted = React.useRef(false);
const isCalled = React.useRef(false);
/******************* interval timer ****************/
/***************************************************/
const {
dateCreated,
started,
chooserGuid,
ownerGuid,
spectators,
pendingPlayers,
players,
settings,
kickedPlayers
} = gameData.game ?? {};
const {
playerGuid
} = userData;
const iWasKicked = !!kickedPlayers?.[playerGuid];
const amInGame = playerGuid in (players ?? {});
useEffect(() => {
const playMode = params.throwaway !== "play" && started && !iWasKicked && amInGame;
const notPlayMode = iWasKicked && params.throwaway === "play";
if (playMode) {
history.push(SiteRoutes.Game.resolve({
id: gameId,
throwaway: "play"
}))
}
if (notPlayMode) {
history.push(SiteRoutes.Game.resolve({
id: gameId,
throwaway: "kicked"
}));
}
}, [started, iWasKicked, amInGame]);
React.useEffect(() => {
if(!isCalled.current) {
isCalled.current = true;
if(started) {
console.log('i fire once')
if(isCalled.current) {
getUpdate();
}
}
}
}, []);
React.useEffect(() => {
if(gameData?.game?.roundStarted) {
if(!isGameStarted.current) {
console.log("round is started");
isGameStarted.current = true;
}
}
}, [gameData]);
const skipPlayer = (game_string_id: any, target_turn: any, chooserGuid: any) => {
return GameDataStore.skipPlayer(game_string_id, target_turn, chooserGuid);
}
const interval = () => {
let timer = setSeconds, minutes, seconds;
let chooserGuid = localStorage.getItem('chooserGuid');
let game_string_id = localStorage.getItem('game_id');
let target_turn = localStorage.getItem('target_turn');
let is_called = localStorage.getItem('is_called');
if(!isGameStarted.current) {
console.log("isGameStarted : "+isGameStarted.current);
if (typeof timer !== undefined && timer != null) {
minutes = parseInt(timer / 60 as any, 10);
seconds = parseInt(timer % 60 as any, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
//console.log("test");
console.log(minutes + ":" + seconds);
setUpdateShowTimer(minutes+":"+seconds);
if (timer == 0) {
skipPlayer(game_string_id, target_turn, chooserGuid);
if(intervalData != undefined && intervalData!== null)
clearInterval(intervalData);
}
if (--timer < 0) {
if(intervalData != undefined && intervalData!== null)
clearInterval(intervalData);
}
setSeconds -= 1;
}
}
}
const startTimer = () => {
console.log("called again");
//interval_counter = setInterval(interval,1000);
setIntervalData(setInterval(interval,1000));
}
const getUpdate = () => {
if(gameData?.game?.players && gameData?.game?.id) {
let game_id = gameData.game.id;
let all_players = gameData.game.players;
let all_player_id = Object.keys(all_players);
let filteredAry = all_player_id.filter(e => e !== userData.playerGuid);
console.log("user player guid:"+userData.playerGuid);
console.log("guid:"+chooserGuid);
console.log("all players:"+all_player_id);
console.log("new array:"+filteredAry);
let target_item = filteredAry.find((_, i, ar) => Math.random() < 1 / (ar.length - i));
if(typeof target_item !== undefined && target_item!=null) {
localStorage.setItem('target_turn',target_item);
}
localStorage.setItem('is_started','0');
if(typeof game_id !== undefined && game_id!=null) {
localStorage.setItem('game_id',game_id);
}
if(typeof chooserGuid !== undefined && chooserGuid!=null) {
localStorage.setItem('chooserGuid',chooserGuid);
}
if(isChooser) {
startTimer();
} else {
//clearInterval(intervalData);
}
}
}
const isOwner = ownerGuid === userData.playerGuid;
const isChooser = playerGuid === chooserGuid;
const amSpectating = playerGuid in { ...(spectators ?? {}), ...(pendingPlayers ?? {}) };
const playerGuids = Object.keys(players ?? {});
const roundsToWin = getTrueRoundsToWin(gameData.game as ClientGameItem);
const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);
const inviteLink = (settings?.inviteLink?.length ?? 0) > 25
? `${settings?.inviteLink?.substr(0, 25)}...`
: settings?.inviteLink;
const meKicked = kickedPlayers?.[playerGuid];
const tablet = useMediaQuery('(max-width:1200px)');
const canChat = (amInGame || amSpectating) && moment(dateCreated).isAfter(moment(new Date(1589260798170)));
const chatBarExpanded = chatData.sidebarOpen && !tablet && canChat;
/**********************************************/
/********************************************/
return (
<div style={{ maxWidth: chatBarExpanded ? "calc(100% - 320px)" : "100%" }}>
<div style={{ minHeight: "70vh" }}>
{iWasKicked && (
<Alert variant={"filled"} severity={"error"}>
<Typography>
{meKicked?.kickedForTimeout ? "You were kicked for being idle. You may rejoin this game any time!" : "You left or were kicked from this game"}
</Typography>
</Alert>
)}
{!winnerGuid && settings?.inviteLink && (
<Typography variant={"caption"}>
Chat/Video Invite: <a href={settings.inviteLink} target={"_blank"} rel={"nofollow noreferrer"}>{inviteLink}</a>
</Typography>
)}
{winnerGuid && (
<ShowWinner />
)}
{!winnerGuid && (
<ErrorBoundary>
{updateShowTimer}
{(!started || !(amInGame || amSpectating)) && (
<BeforeGame gameId={gameId} isOwner={isOwner} />
)}
{started && amInGame && !isChooser && (
[
<GamePlayWhite />
]
)}
{started && amInGame && isChooser && (
[
<GamePlayBlack />
]
)}
{started && amSpectating && (
<GamePlaySpectate />
)}
</ErrorBoundary>
)}
</div>
</div>
);
};
interface BeforeGameProps {
isOwner: boolean;
gameId: string;
}
const BeforeGame: React.FC<BeforeGameProps> = (props) => {
return (
<>
{props.isOwner && (
<GameStart id={props.gameId} />
)}
{!props.isOwner && (
<GameJoin id={props.gameId} />
)}
</>
);
};
Does the double render also occur in Production (i.e. build) mode and are you using Strict Mode?
Strict Mode will render your components twice in dev, which means your useEffect will be called twice.
It must be the gameId that initially has no value (First call to useEffect) and gets populated by Match component of router library (Second call to useEffect)
You can test this by giving a hardcoded value to gameId where it should only call useEffect once:
<GameInner gameId={"5"} /> // hardcoded id variable
Regarding prevention; I think its pretty normal with react, as long as you have proper control inside useEffect implementation.
If someone else finds this post:
I think it's the useMediaQuery that initially returns false even though the resolution is narrow enough for tablet. And then just moments later the correct value is returned and another component is rendered.

How to prevent a react parent component from loading

I am using react to build my app and on the post component there are 3 other child components that are been called in a map function while passing the post props.
On the list component when a user clicks on the like button the post tends to reload, and that is not what I want.
This is my post parent component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
class Posts extends Component {
constructor(props) {
super(props)
this.state = {
sidebaropen: false,
test: '',
posts: []
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.post.posts.length > 0) {
this.setState({ posts: nextProps.post.posts })
console.log('updated')
}
}
componentDidMount() {
this.props.getPosts();
socket.on('posts', data => {
console.log("resiving :" + JSON.stringify(data))
if (Object.keys(this.state.posts).length > 0 && Object.keys(data).length >
0) {
this.setState({ posts: [data[0], ...this.state.posts] })
} else {
this.setState({ posts: data })
}
})
socket.on('id', data => {
console.log(data)
})
console.log('mounted post')
}
}
render(){
const { loading } = this.props.post;
const { posts, } = this.state;
let closebtn
let postContent;
if (posts === null || loading) {
postContent = <Spinner />;
} else {
postContent = <PostFeeds posts={ posts } />;
}
if (this.state.sidebaropen) {
closebtn = <button className='close-toggle-btn' onClick =
{ this.closetoggleclickhandle } />
}
return (
<div className= "post_wrapper" >
<Postsidebar show={ this.state.sidebaropen } />
< div className = "" >
{ postContent }
< /div>
{ closebtn }
<TiPlus className='creat-post-toggle-btn icons' onClick =
{ this.toggleclickhandle } />
</div>
)
}
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
post: state.post
})
export default connect(mapStateToProps, { getPosts })(Posts);
and this is the first child component
import React, { Component } from 'react';
import PostItem from './PostItem';
class PostFeeds extends Component {
componentDidMount() {
//this.setState({ test : 'mounted'})
console.log('mounted feed')
}
render() {
const { posts } = this.props;
//console.log(posts)
return posts.map(post => <PostItem key={ post._id } post = { post } />);
}
}
postItem.js its i little kind of rough
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike, bookmark } from "../../actions/postsActions";
import Postimg from './Postimg';
import { MdBookmarkBorder ,/**MdBookmark */} from "react-icons/md";
import {AiFillDislike, AiOutlineLike, AiFillDownSquare} from 'react-icons/ai'
import { TiHeartOutline, TiHeartFullOutline, TiMessage, TiDelete } from "react-icons/ti";
class PostItem extends Component {
onDeleteClick(id){
this.props.deletePost(id);
}
componentWillMount() {
console.log( 'mounted item')
//console.log(window.scrollTo(0, localStorage.getItem('scrollpossition')))
}
onLikeClick(id){
this.props.addLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
window.location.href = '/feed'
}
onUnlikeClick(id){
this.props.removeLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
window.location.href = '/feed'
}
findUserLike(likes) {
const { auth } = this.props;
if(likes.length > 0){
if(likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
}
findUserDislike(dislikes) {
const { auth } = this.props;
if(dislikes.length > 0){
if(dislikes.filter(dislike => dislike.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
}
onBookmark (id){
this.props.bookmark(id)
}
render() {
const { post, auth, showActions } = this.props;
let ifAlreadyliked;
let ifAlreadydisliked;
let postimg;
let postText;
let profileimg
let topic = ''
if(post.user)
{
if(post.user.profileImageData){
profileimg = <Link to={`/profile/${post.profile.handle}`}><img src={post.profile.profileImageData} alt='' /></Link>
}else{
profileimg = <img src='/assets/images/user-4.png' alt='pip' />
}
if(this.findUserLike(post.likes)){
ifAlreadyliked = <TiHeartFullOutline className= 'icons like-color'/>
}else{
ifAlreadyliked = <TiHeartOutline className= 'icons'/>
}
if(this.findUserDislike(post.dislikes)){
ifAlreadydisliked = <AiFillDislike className= 'icons yellow'/>
}else{
ifAlreadydisliked = <AiOutlineLike className= 'icons' onClick={this.onUnlikeClick.bind(this, post._id)}/>
}
}
if(post.Topic){
topic = <div className= ''><small><b style={{color:'#ff8d00'}}>< AiFillDownSquare />{post.Topic}</b></small></div>
}
if(post.postImageData !== '' && post.postImageData !== null && post.postImageData !== undefined)
{
postimg = <Postimg imageSrc = {post.postImageData} imgAlt = {''}/>
}
if(post.text !== '' || post.text === null)
{
postText = <div className='feed_text'>{post.text}</div>
}
return (
<div className="feed_card">
<div className="feed_header">
<div className="feed-profile-img">{profileimg}</div>
<div className="feed-handle-text">{post.name}</div>
<div className='v-spacer'/>
<div className="time-stamp"><small>{new Date (post.date).toLocaleString ('en-US', {hour: 'numeric', hour12: true, minute: 'numeric', month: 'long', day: 'numeric' } )} </small></div>
</div>
<div className="feed-body-container">
<div>
{topic}
{postimg}
{postText}
<div className='mini_feed_footer'>
<small>{post.likes.length} likes. {post.comments.length} comments. {post.dislikes.length} dislikes.</small>
</div>
{ showActions ? (
<div className='feed_footer'>
<div onClick={this.onLikeClick.bind(this, post._id)} type="button" className="btn btn-light mr-1">
<div className='feed-icon-mini-container'>
{ifAlreadyliked}
</div>
</div>
<div className='spacer'/>
{ifAlreadydisliked}
<div className='spacer'/>
<Link to={`/post/${post._id}`} className='header-brand'>
<TiMessage className='icons'/>
</Link>
<div className='spacer'/>
{ post.user === auth.user.id ? (
<TiDelete
onClick={this.onDeleteClick.bind(this, post._id)}
className="icons red"
/>
) : <MdBookmarkBorder
onClick={this.onBookmark.bind(this, post._id)}
className="icons blue"
/> }
</div>) : null}
</div>
</div>
</div>
);
}
}
PostItem.defaultProps = {
showActions: true
}
PostItem.propTypes = {
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
deletePost: PropTypes.func.isRequired,
addLike:PropTypes.func.isRequired,
removeLike:PropTypes.func.isRequired,
bookmark:PropTypes.func.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
})
export default connect(mapStateToProps, {deletePost, addLike,bookmark, removeLike})(PostItem);
Don't use window object for navigation (Major) as this causes reload. Make your handlers arrow functions (minor).
onLikeClick = id => {
this.props.addLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
this.props.history.push("/feed");
};
onUnlikeClick = id => {
this.props.removeLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
this.props.history.push("/feed");
};
Also if /feed is the same page then remove it all together, no need for it.

ReactJS -- Unable to find latest title from an api causing error

I have a Landing component and a NewsLatest component. I am hitting on an api and trying to find the article with the latest timestamp but iam unable to get it done in reactJS.I checked the js code its working fine but in react it is not rendering. Kindly suggest something.
import React, { Component } from 'react'
import NewsSearch from '../NewsSearch/NewsSearch';
import NewsLatest from '../NewsLatest/NewsLatest';
import './Landing.css';
import axios from 'axios';
class Landing extends Component {
state={
newsList: []
}
componentDidMount(){
axios.get(`https://api.nytimes.com/svc/topstories/v2/home.json?api-key=7cK9FpOnC3zgoboP2CPGR3FcznEaYCJv`)
.then(res=> {
this.setState({newsList: res.data.results});
});
}
render() {
// console.log(this.state.newsList);
return (
<div className="landing text-center text-white">
<h1>News Portal</h1>
<div className="news-search">
<NewsSearch />
</div>
<div className="news-latest">
<NewsLatest newsList={this.state.newsList}/>
</div>
</div>
)
}
}
export default Landing;
import React, { Component } from 'react';
// import PropTypes from 'prop-types';
class NewsLatest extends Component {
constructor(props){
super(props);
this.state = {
newsTitle:'',
abstract:'',
newsUrl:'',
}
// this.newsLatest = this.newsLatest.bind(this);
}
newsLatest = (e)=>{
// e.preventDefault();
const {newsList} = this.props;
let maxTime = newsList.map(function(o) {
return new Date(o.updated_date);
});
let maximumValue = Math.max(...maxTime);
let latestnews = newsList.filter(function (el) {
return maximumValue === new Date(el.updated_date).getTime();
})[0];
if(latestnews){
this.setState({newsTitle: latestnews.title});
return (<h4>{this.state.newsTitle}</h4>);
}
}
newsTitle = () => (
this.props.newsList.map(item => (<h2 key={item.title}>{item.title}</h2>))
)
render() {
console.log(this.props.newsList);
return (
<div>
<h2>News Latest....</h2>
{this.newsLatest()}
</div>
);
}
}
export default NewsLatest;
There is some issue in rendering in NewsLatest component. KIndly suggest something.
Try this:
You must probably be getting a maximum depth error, use a lifecycle method instead like componentDidUpdate. Update your component state only if the previous props are different from the newer ones.
Read more here: https://reactjs.org/docs/react-component.html
import React, { Component } from "react";
// import PropTypes from 'prop-types';
class NewsLatest extends Component {
constructor(props) {
super(props);
this.state = {
newsTitle: "",
abstract: "",
newsUrl: ""
};
// this.newsLatest = this.newsLatest.bind(this);
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.newsList !== this.props.newsList) {
const { newsList } = this.props;
let maxTime = newsList.map(function(o) {
return new Date(o.updated_date);
});
let maximumValue = Math.max(...maxTime);
let latestnews = newsList.filter(function(el) {
return maximumValue === new Date(el.updated_date).getTime();
})[0];
this.setState({ newsTitle: latestnews.title });
}
}
// newsLatest = e => {
// // e.preventDefault();
// const { newsList } = this.props;
// let maxTime = newsList.map(function(o) {
// return new Date(o.updated_date);
// });
// let maximumValue = Math.max(...maxTime);
// let latestnews = newsList.filter(function(el) {
// return maximumValue === new Date(el.updated_date).getTime();
// })[0];
// console.log(latestnews)
// if (latestnews && latestnews.hasOwnProperty('length') && latestnews.length>0) {
// return <h4>{this.state.newsTitle}</h4>;
// }
// };
newsTitle = () =>
this.props.newsList.map(item => <h2 key={item.title}>{item.title}</h2>);
render() {
console.log(this.props.newsList);
return (
<div>
<h2>News Latest....</h2>
<h4>{this.state.newsTitle}</h4>
</div>
);
}
}
export default NewsLatest;
Also, a sandbox: https://codesandbox.io/s/hungry-frog-z37y0?fontsize=14

The state of a functional component does not change very well

I am creating a domain integrated search site now and I need to animate the TLD part of the design that the designer gave me.
However, when entering the keyword to be searched by the functional component, the state did not change while the class component was changed.
Code
functional component
import * as React from 'react';
import classNames from 'classnames';
import './SearchBarTLD.scss';
const tldList:string[] = [
'.com',
'.co.kr',
'.se',
'.me',
'.xyz',
'.kr',
'.dev',
'.xxx',
'.rich',
'.org',
'.io',
'.shop',
'.ga',
'.gg',
'.net'
];
const SearchBarTLD = () => {
const [ selectIndex, setSelectIndex ] = React.useState(0);
const changeIndex = () => {
if (selectIndex === 14) {
setSelectIndex(0)
} else {
setSelectIndex(selectIndex + 1)
}
}
React.useLayoutEffect(() => {
const interval = setInterval(() => changeIndex(), 1500);
return () => {
clearInterval(interval)
}
})
const renderList = () =>{
return tldList.map((tld:string, index:number) => {
return (
<span
className={
classNames(
"SearchBarTLD__tld", {
"SearchBarTLD__tld--visual": selectIndex === index
}
)
}
key={tld}
>
{tldList[index]}
</span>
)
})
}
return (
<div className="SearchBarTLD">
{renderList()}
</div>
)
}
export default SearchBarTLD;
class components
import * as React from 'react';
import classNames from 'classnames';
import './SearchBarTLD.scss';
export interface SearchBarTLDProps {
}
export interface SearchBarTLDState {
selectIndex: number,
tldList: string[]
}
class SearchBarTLD extends React.Component<SearchBarTLDProps, SearchBarTLDState> {
state: SearchBarTLDState;
intervalId: any;
constructor(props: SearchBarTLDProps) {
super(props);
this.state = {
selectIndex: 0,
tldList: [
'.com',
'.co.kr',
'.se',
'.me',
'.xyz',
'.kr',
'.dev',
'.xxx',
'.rich',
'.org',
'.io',
'.shop',
'.ga',
'.gg',
'.net'
]
};
this.intervalId = 0;
}
changeIndex() {
const { selectIndex } = this.state;
if (selectIndex === 14) {
this.setState({selectIndex: 0});
} else {
this.setState({selectIndex: selectIndex + 1});
}
}
renderList = () =>{
const { selectIndex, tldList } = this.state;
return tldList.map((tld:string, index:number) => {
return (
<span
className={
classNames(
"SearchBarTLD__tld", {
"SearchBarTLD__tld--visual": selectIndex === index
}
)
}
key={tld}
>
{tldList[index]}
</span>
)
})
}
componentDidMount() {
this.intervalId = setInterval(() => this.changeIndex(), 1500);
}
componentWillUnmount() {
clearInterval(this.intervalId)
}
render() {
return (
<div className="SearchBarTLD">
{this.renderList()}
</div>
);
}
}
export default SearchBarTLD;
The results of the functional component and the results of the class component are shown below.
funcational component
https://user-images.githubusercontent.com/28648915/56777865-8c0cf100-680e-11e9-93ad-cb7b59cd54e9.gif
class component
https://user-images.githubusercontent.com/28648915/56777941-e4dc8980-680e-11e9-8a47-be0a14a44ed9.gif
Can you guys tell me why this happens?

react doesn't rerender component when props changed

I'm changing the class attribute of my props and then i want the component to rerender with the new classes but that doesn't work. I've read about the shouldComponentUpdate method but that method never gets called.
var ReactDOM = require('react-dom');
var React = require('react');
class Button extends React.Component {
constructor(props) {
super(props);
console.log("BUTTON")
console.log(props);
var options = props.options;
}
componentWillMount () {
var defaultFeatureOptionId = this.props.feature.DefaultFeatureOptionId;
this.props.options.forEach((option) => {
var classes = "";
if (option.Description.length > 10) {
classes = "option-button big-button hidden";
} else {
classes = "option-button small-button hidden";
}
if (option.Id === defaultFeatureOptionId) {
classes = classes.replace("hidden", " selected");
option.selected = true;
}
option.class = classes;
});
}
shouldComponentUpdate(props) {
console.log("UPDATE");
}
toggleDropdown(option, options) {
console.log(option);
console.log(options)
option.selected = !option.selected;
options.forEach((opt) => {
if (option.Id !== opt.Id) {
opt.class = opt.class.replace("hidden", "");
}
else if(option.Id === opt.Id && option.selected) {
opt.class = opt.class.replace("", "selected");
}
});
}
render() {
if (this.props.options) {
return (<div> {
this.props.options.map((option) => {
return <div className={ option.class } key={option.Id}>
<div> {option.Description}</div>
<img className="option-image" src={option.ImageUrl}></img>
<i className="fa fa-chevron-down" aria-hidden="true" onClick={() => this.toggleDropdown(option, this.props.options) }></i>
</div>
})
}
</div>
)
}
else {
return <div>No options defined</div>
}
}
}
module.exports = Button;
I have read a lot of different thing about shouldComponentUpdate and componentWillReceiveProps but there seems to be something else i'm missing.
You cannot change the props directly, either you call a parent function to change the props that are passed to your component or in your local copy that you createm you can change them. shouldComponentUpdate is only called when a state has changed either directly or from the props, you are not doing any of that, only modifying the local copy and hence no change is triggered
Do something like
var ReactDOM = require('react-dom');
var React = require('react');
class Button extends React.Component {
constructor(props) {
super(props);
console.log(props);
this.state = {options = props.options};
}
componentWillRecieveProps(nextProps) {
if(nextProps.options !== this.props.options) {
this.setState({options: nextProps.options})
}
}
componentWillMount () {
var defaultFeatureOptionId = this.props.feature.DefaultFeatureOptionId;
var options = [...this.state.options]
options.forEach((option) => {
var classes = "";
if (option.Description.length > 10) {
classes = "option-button big-button hidden";
} else {
classes = "option-button small-button hidden";
}
if (option.Id === defaultFeatureOptionId) {
classes = classes.replace("hidden", " selected");
option.selected = true;
}
option.class = classes;
});
this.setState({options})
}
shouldComponentUpdate(props) {
console.log("UPDATE");
}
toggleDropdown(index) {
var options = [...this.state.options];
var options = options[index];
option.selected = !option.selected;
options.forEach((opt) => {
if (option.Id !== opt.Id) {
opt.class = opt.class.replace("hidden", "");
}
else if(option.Id === opt.Id && option.selected) {
opt.class = opt.class.replace("", "selected");
}
});
this.setState({options})
}
render() {
if (this.state.options) {
return (<div> {
this.state.options.map((option, index) => {
return <div className={ option.class } key={option.Id}>
<div> {option.Description}</div>
<img className="option-image" src={option.ImageUrl}></img>
<i className="fa fa-chevron-down" aria-hidden="true" onClick={() => this.toggleDropdown(index) }></i>
</div>
})
}
</div>
)
}
else {
return <div>No options defined</div>
}
}
}
module.exports = Button;

Resources