The state of a functional component does not change very well - reactjs

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?

Related

Mobx observer won`t update component

So I have TodoList component which is updated just fine when I add new Item. Inside I have TodoItem width delete and togglecomplete functions and they do invoke methodsof my todoList object, I see in console that object is changing, yet List component won't get rerendered. Any idia what can be done.
package json: "mobx": "^6.7.0", "mobx-react-lite": "^3.4.0",
import { ITodo } from '../type/ITodo';
import TodoItem from '../TodoItem/TodoItem.component'
import './TodoList.styles.css';
import { observer } from 'mobx-react-lite';
type Props = {
todoList: ITodo[];
}
const TodoList: React.FC<Props> = ({ todoList }) => {
return (
<div className="TodoList">
{
todoList && todoList.length > 0 && (
todoList.map(el => (
<TodoItem content={el} key={el.id} />
))
)
}
</div>
)
}
export default observer(TodoList);
here are delete and toggle complete functions
import { useStores } from '../Store/StoreContext';
import { observer } from 'mobx-react-lite';
type Props = {
content: ITodo;
}
const TodoItem: React.FC<Props> = ({ content }) => {
const { todoList } = useStores();
const { deadline, title , description, completed, id } = content
const handleChange = () => {
todoList.toggleComplete(id)
}
const deleteHandler = () => {
todoList.deleteTodo(id);
}
return (
<div className="TodoItem">
<div className="actions">
<FormControlLabel
onChange={handleChange}
/>
<Button
variant="outlined"
onClick={deleteHandler}
>
Delete
</Button>
</div>
</div>
)
}
export default observer(TodoItem);
and here is my store just in case, I keep my store in Context.
import { action, makeObservable, observable } from "mobx";
import { ITodo } from "../type/ITodo";
export class Todo {
todos: ITodo[] = [];
constructor() {
makeObservable(this, {
todos: observable,
getTodos: action,
addTodo: action,
deleteTodo: action,
toggleComplete: action,
});
}
getTodos() {
return this.todos;
}
addTodo(todo: ITodo) {
this.todos.push(todo);
}
deleteTodo(id: string) {
console.log(id);
this.todos = this.todos.filter((el) => el.id !== id);
}
toggleComplete(id: string) {
this.todos = this.todos.map((el) => {
if (el.id !== id) {
return el;
}
return { ...el, completed: !el.completed };
});
}
}
Here is repository on github: https://github.com/pavel-gutsal/mobx-todo-list
node version - 16.xx

Using typed.js with React function components

typed.js doesn't offer an example for this in the docs, only for class components:
class TypedReactDemo extends React.Component {
componentDidMount() {
const options = { ... };
this.typed = new Typed(this.el, options );
}
render() {
return (
<span ref={(el) => { this.el = el; }} />
);
}
}
import React, { useRef, useEffect } from "react";
import Typed from "typed.js";
const Example = () => {
const typeTarget = useRef(null);
useEffect(() => {
const typed = new Typed(typeTarget.current, {
strings: ["<i>First</i> sentence.", "& a second sentence."],
typeSpeed: 40,
});
return () => {
typed.destroy();
};
}, []);
return <span ref={typeTarget} />;
};
export default Example;

random quote machine code:The WebDev Coach (youtube)-error shows for this.selectedQuote in render() function

import React, {Component} from 'react';
import './App.css';
import Button from './components/Button';
import random from 'lodash';
class App extends Component {
super(props) {
constructor(props)
this.state =
{
quotes: [],
selectedQuoteIndex: null,
}
this.selectQuoteIndex = this.selectQuoteIndex.bind(this);
}
componentDidMount() {
fetch('https://gist.githubusercontent.com/natebass/b0a548425a73bdf8ea5c618149fe1fce/raw/f4231cd5961f026264bb6bb3a6c41671b044f1f4/quotes.json')
.then(data => data.json())
.then(quotes => this.setState({quotes}))
.then(() => this.setState({selectedQuoteIndex: this.selectQuoteIndex()}))
}
nextQuoteClickHandler() {
console.log("click");
}
selectQuoteIndex() {
if (!this.state.quotes.length) {
return;
}
return random(0, this.state.quotes.length - 1)
}
get selectedQuote() {
if (!this.state.quotes.length || !Number.isInteger(this.state.selectQuoteIndex)) {
return;
}
return this.state.quotes[this.state.selectedQuoteIndex];
}
render() {
return (
<div className="App" id="quote-box">
<Button buttonDisplayName="next click!" clickHandler={this.nextQuoteClickHandler}/>
{this.selectedQuote ? this.selectedQuote.quote : ''}
</div>
);
}
}
export default App;
This will fix your code.
import React, { Component } from 'react';
import './App.css';
function getRandomNumber(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
let random = Math.floor(Math.random() * (max - min + 1)) + min;
return random;
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
quotes: [],
selectedQuoteIndex: null
}
this.selectQuoteIndex = this.selectQuoteIndex.bind(this);
}
componentDidMount() {
fetch('https://gist.githubusercontent.com/natebass/b0a548425a73bdf8ea5c618149fe1fce/raw/f4231cd5961f026264bb6bb3a6c41671b044f1f4/quotes.json')
.then(data => data.json())
.then(quotes => this.setState({ quotes: quotes }))
.then(() => this.setState({ selectedQuoteIndex: this.selectQuoteIndex() }));
}
nextQuoteClickHandler() {
this.setState({
selectedQuoteIndex: getRandomNumber(1, this.state.selectedQuoteIndex)
})
}
selectQuoteIndex() {
if (!this.state.quotes.length) {
return;
}
return getRandomNumber(1, this.state.quotes.length - 1)
}
render() {
console.log('index', this.state.selectedQuoteIndex)
return (
<div className="App" id="quote-box">
<button onClick={() => this.nextQuoteClickHandler()} >next click!</button>
<p>{this.state.selectedQuoteIndex ? this.state.quotes[this.state.selectedQuoteIndex].quote : ''}</p>
</div>
);
}
}
export default App;
The function getRandomNumber() generates random numbers but after some time it starts generating the same numbers.

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.

React App UI Isn't Updating After `sortAlphabetically` Function is Run

I have a React app that displays a grid of cars that I'd like to be able to sort on the click of a button.
While I have this.setState implemented towards the end of the sortAlphabetically function, it isn't being reflected on the web page.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import CarCard from '../components/CarCard';
import CarForm from './CarForm';
import './Cars.css';
import { getCars } from '../actions/cars';
import { sortCar } from '../actions/cars';
Component.defaultProps = {
cars: { cars: [] }
}
class Cars extends Component {
constructor(props) {
super(props)
this.state = {
cars: [],
sortedCars: []
};
}
sortAlphabetically = () => {
console.log("sort button clicked")
const newArray = [].concat(this.props.cars.cars)
const orgArray = newArray.sort(function (a,b) {
var nameA = a.name.toUpperCase();
var nameB = b.name.toUpperCase();
if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
}, () => this.setState({ cars: orgArray }))
console.log(orgArray)
this.props.sortCar(orgArray);
}
componentDidMount() {
this.props.getCars()
this.setState({cars: this.props.cars})
}
render() {
return (
<div className="CarsContainer">
<h3>Cars Container</h3>
<button onClick={this.sortAlphabetically}>Sort</button>
{this.props.cars.cars && this.props.cars.cars.map(car => <CarCard key={car.id} car={car} />)}
{/* {this.state.cars.cars && this.state.cars.cars.map(car => <CarCard key={car.id} car={car} />)} */}
<CarForm />
</div>
);
}
}
const mapStateToProps = (state) => {
return ({
cars: state.cars
})
}
const mapDispatchToProps = (dispatch) => {
return {
sortCar: (cars) => dispatch(sortCar(cars)),
getCars: (cars) => dispatch(getCars(cars))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cars);
Looking in the console, the orgArray does in fact alphabetize the array. The problem is, the UI isn't updating because of some disconnect between props and state.
Why is mapStateToProps not bridging the two together so that a change in the array re-runs render?
If you want to see updated changes, you have to read "cars" from the state instead of reading it from the props.
{this.state.cars && this.state.cars.map(car => <CarCard key={car.id} car={car} />)}

Resources