Does state persist upon rerender of components? - javascript

I wanna know why will my state remain the same upon rerender of the components.
Here is my parent component
import React, {useEffect} from 'react';
import {connect} from "react-redux"
import NoteList from '../Components/NoteList';
import NoteDetail from '../Components/NoteDetail';
import "./NotePage.scss"
const NotePage = ({note}) => {
const selectNote = (item) => {
if (!item){
return <div className="emptySection"/>
}
console.log("return a new component")
return <NoteDetail/>
}
return (
<div className="NotePage">
<div className="noteList">
<NoteList/>
</div>
<div className="noteDetail">
{selectNote(note)}
</div>
</div>
)
}
const mapState = (state) => (
{
note: state.notesReducer.note
}
)
export default connect(mapState)(NotePage);
I have already checked that this component rerender when the note in redux store changed. The selectNote is executed proper as well. But does it return a brand new component of NoteDetail?
Here is my child component:
import React, {useState, useRef, useEffect} from 'react';
import {connect} from "react-redux";
import Editor from 'draft-js-plugins-editor';
import {EditorState, ContentState} from "draft-js";
import CreateInlineToolbarPlugin from "draft-js-inline-toolbar-plugin";
import CustomInlineToolbar from "./CustomInlineToolbar";
import {updateNote} from "../redux/action"
import "./NoteDetail.scss";
import 'draft-js-inline-toolbar-plugin/lib/plugin.css';
const InlineToolbarPlugin = CreateInlineToolbarPlugin();
const {InlineToolbar} = InlineToolbarPlugin;
const NoteDetail = ({updateNote, title, note, date, _id}) => {
let initContentState = ContentState.createFromText(note);
let [editorState, setEditorState] = useState(EditorState.createWithContent(initContentState));
let [titleState, setTitleState] = useState(title)
let editorRef = useRef(React.createRef());
useEffect(()=>{
updateNote(_id, titleState, editorState)
},[editorState, titleState, _id])
const focus = () => {
editorRef.current.focus()
}
return(
<div>
<div className="NoteDetail-container">
<input type="text" id="title"
value={titleState === "Untitled Page"? "":titleState}
onChange={(e)=>setTitleState(e.target.value)}
/>
<div id="content" onClick={focus}>
<Editor
plugins={[InlineToolbarPlugin]}
onChange={(e)=>setEditorState(e)}
editorState={editorState}
ref={ele => editorRef.current = ele}
/>
<CustomInlineToolbar InlineToolbar={InlineToolbar} />
</div>
</div>
</div>
)
};
const mapProps = (state) => {
const {title, _id, note, date} = state.notesReducer.note
return {
title,
_id,
note,
date
}
}
export default connect (mapProps, {updateNote})(NoteDetail)
It rerenders when the note state changed, but the state remain the same. I checked that the title did change, but the titleState didn't. So how does state and component comparison behave in React?
my reducer:
import {
GET_NOTE,
UPDATE_NOTE,
DEL_NOTE,
CREATE_NOTE,
SELECT_NOTE,
GET_NOTES,
} from "../action/types";
import {
api_getNote,
api_delNote,
api_updateNote,
api_postNote
} from "../../api/noteService"
//TODOS: finish update_title part
const notesReducer = ( state={}, action) => {
switch (action.type){
case GET_NOTES:
return{notes: action.payload}
case UPDATE_NOTE:
const {id, content} = action.payload
api_updateNote(id,content)
return state
case DEL_NOTE:
api_delNote(action.payload)
return state
case CREATE_NOTE:
api_postNote(action.payload, (res)=> {
return {notes: res}
})
return state
case SELECT_NOTE:
return {...state, note: action.payload}
default:
return state
}
}
export default notesReducer

Related

React (Next) won't re-render after redux state change (yes, state returned as new object)

I am facing a problem with re-rendering after a state change in my NextJS app.
The function sendMessageForm launches a redux action sendMessage which adds the message to the state.
The problem is unrelated to the returned state in the reducer as I am returning a new object(return {...state}) which should trigger the re-render!
Is there anything that might block the re-render ?
This is the file that calls & displays the state, so no other file should be responsible ! But if you believe the problem might lie somewhere else, please do mention !
import { AttachFile, InsertEmoticon, Mic, MoreVert } from '#mui/icons-material';
import { Avatar, CircularProgress, IconButton } from '#mui/material';
import InfiniteScroll from 'react-infinite-scroller';
import Head from 'next/head';
import { useState, useEffect } from 'react';
import Message from '../../components/Message.component';
import styles from '../../styles/Chat.module.css'
import { useRouter } from 'next/router'
import {useSelector, useDispatch} from "react-redux"
import {bindActionCreators} from "redux"
import * as chatActions from "../../state/action-creators/chatActions"
const Chat = () => {
const router = useRouter()
const { roomId } = router.query
const auth = useSelector((state)=> state.auth)
const messages = useSelector((state)=> state.chat[roomId].messages)
const dispatch = useDispatch()
const {getMessages, markAsRead, sendMessage} = bindActionCreators(chatActions, dispatch)
const [inputValue, setInputValue] = useState("")
const sendMessageForm = (e) => {
e.preventDefault()
console.log("***inputValue:", inputValue)
sendMessage(roomId, inputValue)
}
const loadMessages = (page) => {
if(roomId)
getMessages(roomId, page)
}
//user-read-message
useEffect(() => {
//user-read-message
markAsRead(roomId, auth.user._id)
}, [messages]);
return (
<div className={styles.container}>
<Head>
<title>Chat</title>
</Head>
<div className={styles.header}>
<Avatar/>
<div className={styles.headerInformation}>
<h3>Zabre el Ayr</h3>
<p>Last Seen ...</p>
</div>
<div className={styles.headerIcons}>
<IconButton>
<AttachFile/>
</IconButton>
<IconButton>
<MoreVert/>
</IconButton>
</div>
</div>
<div className={styles.chatContainer}>
<InfiniteScroll
isReverse={true}
pageStart={0}
loadMore={loadMessages}
hasMore={messages.hasNextPage || false}
loader={<div className={styles.loader} key={0}><CircularProgress /></div>}
>
{Object.keys(messages.docs).map((key, index)=>{
return<Message
key={index}
sentByMe={messages.docs[key].createdBy === auth.user._id}
message={messages.docs[key].msg}
/>})}
</InfiniteScroll>
<span className={styles.chatContainerEnd}></span>
</div>
<form className={styles.inputContainer}>
<InsertEmoticon/>
<input className={styles.chatInput} value={inputValue} onChange={(e)=>setInputValue(e.target.value)}/>
<button hidden disabled={!inputValue} type='submit' onClick={sendMessageForm}></button>
<Mic/>
</form>
</div>)
};
export default Chat;
useSelector requires a new object with a new reference from the object you are passing to it in order to trigger the re-render
What you're doing with return {...state} is just creating a new object for the parent object but not the nested one useSelector is using, which is in your case :
const messages = useSelector((state)=> state.chat[roomId].messages)
So, you should return the whole state as a new object WITH a new state.chat[roomId].messages object
In other words, the references for the root object & the one being used should be changed.

Rendring the component, how much object the state variable have (in React.js)?

I don't know, whether it is correct or wrong while rendering, the component renders the number of objects it has plus one extra time (6 objects + 1), 7 times the component renders, Please suggest to me is it correct or wrong with reason. If I type 5 letters in the 5th task, it renders 30 times, which means in the 5th index, it have 5 objects so 25+(1 extra time in each object *5)=30 times I think it's a huge performance issue.
Create-task.js
import React, {useEffect, useState} from "react";
import store from "../redux/store";
import actions from "../redux/actions";
import '../styles/create-task.scss';
import {useSelector} from "react-redux";
import TaskTemplate from "./task-template";
import SaveTemplate from "./save-template";
const CreateTask=()=> {
// const [templates, setTemplates]=useState([]);
const onTaskCreate = () => {
store.dispatch({type: actions.ON_CREATE_TASK, payload: {'taskCount':taskCount + 1,'save':false}})
addTemplate();
store.dispatch({type:actions.ON_ADD_TEMPLATES, payload:template});
}
const createTask = useSelector(({reducers}) => {
const {taskCount, taskDetails,save,templates} = reducers;
return {taskCount, taskDetails,save, templates};
});
const {taskCount, taskDetails,save,templates} = createTask;
const {userData}=taskDetails;
const globalTemplates=createTask.templates;
let template = globalTemplates;
const addTemplate = () => {
template.push(<div key={`template${taskCount}`} id={`task${taskCount+1}`}><TaskTemplate/></div>);
return template;
}
useEffect(()=>{
// setTemplates(globalTemplates)
},[])
return (
<div>
<div className={"create-task"}>
<div>TASKS <span className={"task-count"}> {taskCount} </span></div>
<div className={"add-task-button"} onClick={onTaskCreate}>
<button>+</button>
</div>
</div>
{templates && templates.map((template)=>template)}
{/*{save?<SaveTemplate/>:templates.map((template)=> template)}*/}
</div>
)
}
export default CreateTask;
Task.js
import React, {useEffect, useState} from "react";
import store from "../redux/store";
import actions from "../redux/actions";
import DatePicker from 'react-date-picker';
import {useSelector} from "react-redux";
import TimePicker from 'react-bootstrap-time-picker';
import {wrapMapToPropsConstant} from "react-redux/lib/connect/wrapMapToProps";
const TaskTemplate=(props)=>{
const [dateValue, dateChange] = useState(new Date());
const [time, timeChange] = useState('10:00');
const tasks = useSelector(({reducers}) => {
const { taskDetails,edit, createTask} = reducers;
return {taskDetails, edit, createTask};
});
const{edit,taskDetails,createTask}=tasks;
const onFieldChange=(e)=>{
let editingTaskId=e.target.parentNode.parentNode.getAttribute("id");
editingTaskId=editingTaskId.split("task")[1]-1;
createTask.map((data,index)=>{
if(index === editingTaskId){
createTask[editingTaskId]["userData"][e.target.name]=e.target.value;
}
})
}
const dateChangeFormat = (val) =>{
var splitDate=val.toLocaleString('hu-HU').substr(0,12);
splitDate=splitDate.replaceAll(" ","").replaceAll(".","-");
return splitDate;
}
return(
<>
<div>
<label>Task Description</label>
<input name={"task_msg"} defaultValue={ props.task && props.task.task_msg && props.task.task_msg } onChange={(e)=>{onFieldChange(e)}}/>
</div>
<div>
<label>Date</label>
<DatePicker
onChange={onDateChange}
value={props.task?props.task.original_date:dateValue}
format={"y-MM-dd"}
name={"task_date"}
clearIcon={""}
/>
</div>
<div>
<label>Time</label>
<TimePicker start="10:00" end="21:00" step={30} onChange={onTimeChange} value={props.task?props.task.task_time:time} />
</div>
<div>
<label>Assign User</label>
<input name={"assigned_user"} defaultValue={ props.task && props.task.task_msg && props.task.assigned_user} onChange={(e)=>{onFieldChange(e)}}/>
</div>
<div>
<button onClick={(e)=>onFormSave(e)}> Save </button>
</div>
</>
)
}
export default TaskTemplate;
Reducer. js
import actions from "./actions";
const stateInit={
taskCount:0,
taskDetails:[],
templates:[],
createTask:[],
userData: {
task_msg: '',
task_date: '',
task_time: 36000,
assigned_user: '',
time_zone: new Date().getTimezoneOffset(),
is_completed: 0,
}
};
export default function tasks(state=stateInit, action){
switch (action.type){
case actions.ON_CREATE_TASK:{
const {taskCount,save}=action.payload;
return {
...state,
taskCount: taskCount,
save:save,
createTask: [...state.createTask,{userData:{...state.userData}}],
}
}
case actions.ON_VALUE_CHANGE:{
return {
...state,
createTask: action.payload,
}
}
case actions.ON_ADD_TEMPLATES:{
return{
...state,
templates: action.payload,
}
}
default:{
return {
...state
}
}
}
}
Output :
The short answer is that you do not control when the framework decides to re render objects that are bound to elements in the dom tree. It sort of detects that for you.
The long answer is to understand the lifecycle-and-state of objects as well as two-way bindings and what triggers render-updates:
https://reactjs.org/docs/state-and-lifecycle.html
https://react-cn.github.io/react/docs/two-way-binding-helpers.html

Edit the element from an array with React and Redux

I want to edit one element from an array with react and redux.
My problem is I set once the state of array which I map. And in this map I try to change this element with reducer.
Is it possible?? I try to use Object.assing() to avoid mutate the state, BUT I must mutate the state. Isn`t it true?
Below the reducer:
import * as actionTypes from '../actions';
const iniState = {
components: []
};
const rootReducer = (state = iniState, action) => {
switch (action.type) {
case actionTypes.ADD_COMPONENT:
const newComponent = {
id: Math.random(),
co: action.data.compToReducer
}
return {
...state,
components: state.components.concat(newComponent)
};
case actionTypes.DELETE_COMPONENT:
return {
...state,
components: state.components.filter(component=> component.id !== action.index)
}
case actionTypes.EDIT_COMPONENT:
return
Object.assing({}, state, {co: state.co = action.data.componentToReducer})
}
return state;
}
export default rootReducer;
And the container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import FoodComponent from '../../components/FoodComponent/FoodComponent';
import AddComponent from '../../components/Buttons/AddComponent';
import * as actionTypes from '../../store/actions';
import classes from './FoodComponents.scss';
class FoodComponents extends Component {
render() {
return (
<div>
<AddComponent
text="Add component"
click={this.props.onAddComponent}
/>
<ul>
{
this.props.compons.map(component=>(
<li key={component.id}
>
<p className={classes.Component}>{component.co}</p>
<input
type="text"
/>
<button
onClick={this.props.onEditComponent}>
Edit Component
</button>
<button
onClick={()=>this.props.onDeleteComponent(component.id)}>
Delete component
</button>
</li>
))
}
</ul>
</div>
)
}
}
const mapStateToProps = state => {
return {
compons: state.components
}
}
const mapDispatchToProps = dispatch => {
return {
onAddComponent: (component) => dispatch({type: actionTypes.ADD_COMPONENT, data: {compToReducer: component}}),
onDeleteComponent: (id) => dispatch({type: actionTypes.DELETE_COMPONENT, index: id }),
onEditComponent: (component, id) => dispatch({type: actionTypes.EDIT_COMPONENT, data:{componentToReducer: component, index: id}})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(FoodComponents);
onEditComponent: (component, id) => dispatch({type: actionTypes.EDIT_COMPONENT, data:{componentToReducer: component, index: id}})
<button onClick={this.props.onEditComponent}>
Edit Component
</button>
This won't work as you try to pass SyntheticEvent to the reducer. Synthetic events get nullified after callback executes.

How to dispatch onchange event in redux?

I have created a search box inside component. So I am calling onchange function outside in parent App.js. Now I am trying to dispatch that function if I type anything in search box but I can't access that function outside my class.
How to dispatch my function?
Please find my source code below:
import React, {Component} from "react";
import {connect} from "react-redux";
import { User } from "../components/User";
import { Main } from "../components/Main";
import Data from "../components/Data";
import MovieListing from '../components/MovieListing';
import Header from '../components/Header'
import { setName, getApiData } from "../actions/userActions";
import {apiFetch} from "../actions/dataActions"
import {searchFetch} from "../actions/searchActions"
class App extends Component {
constructor(props){
super(props)
this.searchQuery = this.searchQuery.bind(this);
}
searchQuery( query ) {
}
render() {
let dataSet=this.props.data.data.results;
let imagePath = []
let original_title = []
let release_date = []
let original_language = []
if(dataSet){
dataSet.forEach(function (value, key) {
imagePath.push(<Data key={key} imagePath={value.backdrop_path} release_date={value.release_date} original_title={value.original_title} original_language={value.original_language} />)
original_title.push(value.original_title)
})
return(
<div className="wrapper">
<Header searchQuery = { this.searchQuery } />
<div className="movies-listing">
<div className="container">
<MovieListing imagePath={imagePath} release_date={release_date} original_title={original_title} original_language={original_language} />
</div>
</div>
</div>
)
}else{
return(
<div className="middle-loader">
<h1>Loading</h1>
</div>
)
}
// console.log("this.props",this.props);
}
}
const mapStateToProps = (state) => {
return {
user: state.user,
math: state.math,
data: state.data,
searchData: state.searchData
};
};
const mapDispatchToProps = (dispatch) => {
return dispatch(apiFetch()), {searchQuery: (query) => {searchFetch(query)}}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Here I can't access that {searchQuery: (query) => {searchFetch(query)}} because of not accessible that function outside class.
HELP WOULD BE APPRECIATED!!
mapDispatchToProps takes/passes the dispatch function and then return searchQuery function as a prop.
const mapDispatchToProps = (dispatch) => {
return {
searchQuery: (query) => { dispatch(searchFetch(query)) }
}
};
Then in the Header component pass the searchQuery prop
<Header searchQuery={ this.props.searchQuery } />

React Redux - Setting Store and Updating UI using Navigation Menu

I'm extremely new to react/redux and very mediocre at Javascript, but I've been struggling with this for over a week now. Here's what I'm trying to do:
Clicking one of the navigation menu items on the left should dispatch an action to set the selected index in the store (at this point the entire store is just a number). When the index is updated it should automatically be reflected in the UI, at least by changing the css class of the selected item, but eventually it should toggle visibility for content components on the right.
Sidebar.js:
import React, { Component } from 'react';
import SidebarItem from './SidebarItem'
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export default class Sidebar extends Component
{
handleClick(index)
{
//Dispatch action here maybe?
this.props.selectedSidebarItem(index);
console.log(this);
}
render()
{
var sidebarItemNames = ["Verify Original Contract", "Evaluate Transfer Terms", "Create Future Customer", "Check Credit", "Generate Agreement", "Finalize Transfer"];
return (
<div>
<div id="sidebar-title">
<div id="sc-logo">
LOGO
</div>
<div>Contract Transfer for:</div>
<div>XYZ</div>
<br />
</div>
<ul className="list-group" id="sidebar-list">
{sidebarItemNames.map(function(n, index)
{
return <SidebarItem key={index} index={index} selectedIndex={this.selectedSidebarItem} name={n} handleClick={this.handleClick(index).bind(this)} />;
})}
</ul>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return {
selectedSidebarItem: (index) => dispatch(ActionCreators.setSelectedSidebarItem(index))
}
}
const conn = connect(
null,
mapDispatchToProps
)(Sidebar)
SidebarItem.js:
import React, { Component } from 'react';
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export class SidebarItem extends Component {
constructor(props) {
super(props);
}
setSelectedSidebarItem() {
this.props.handleClick(this.props.index);
this.props.selectedSidebarItem(this.props.index);
// const ul = document.getElementById('sidebar-list');
// const items = ul.getElementsByTagName('li');
// for (let i = 0; i < items.length; ++i) {
// items[i].classList.remove('sidebar-item-current');
// }
}
render() {
return (
<li className={"list-group-item sidebar-list-item sidebar-item-todo" + (this.props.index==this.props.selectedIndex? ' sidebar-item-current':'') } onClick={this.setSelectedSidebarItem.bind(this)}><i className="fa fa-circle fa-lg"></i> <span>{this.props.name}</span></li>
)
}
}
Store.js:
import { createStore } from 'redux'
import reducers from './Reducers'
const store = createStore(reducers)
export default store
Reducers.js
const initialState = {
selectedSidebarItem: window.initialPageState,
otherStuff: 5
};
const reducers = (state = initialState, action) => {
switch (action.type) {
case "SET_SELECTED_SIDEBAR_ITEM":
console.log("clicked sidebar index: " + action.index);
var result = Object.assign({}, state, {
selectedSidebarItem: action.index
})
console.log(result);
return result;
default:
return state
}
}
export default reducers
actions.js:
import constants from './constants'
let ActionCreators = {
setSelectedSidebarItem(index) {
var actionObject = {
type: constants.UPDATE_SELECTED_SIDEBAR_ITEM,
index
}
console.log("setting sidebar item", actionObject);
return actionObject
}
}
export default ActionCreators
Constants.js
const constants = {
UPDATE_SELECTED_SIDEBAR_ITEM: "UPDATE_SELECTED_SIDEBAR_ITEM",
ADD_ERROR: "ADD_ERROR",
CLEAR_ERROR: "CLEAR_ERROR"
};
export default constants;
I've tried a few variations of the above and have previously been able to dispatch actions, but am unsure the store is ever updated and nothing is reflected on the UI. Right now I'm getting this error when clicking sidebar items: "Cannot read property 'handleClick' of undefined"
Thanks for any help in advance.
in your sidebar.js:
instead of
handleClick={() => this.handleClick(index).bind(this)}
try this:
handleClick={this.handleClick(index).bind(this)}
And in handleClick method you have to dispatch action:
this.props.selectedSidebarItem(index)
Answer update:
import React, { Component } from 'react';
import SidebarItem from './SidebarItem'
import ActionCreators from '../actions'
import { connect } from 'react-redux'
export default class Sidebar extends Component
{
handleClick(index)
{
//Dispatch action here maybe?
this.props.selectedSidebarItem(index);
this.selectedSidebarItem = index;
console.log(this);
}
render()
{
var sidebarItemNames = ["Verify Original Contract", "Evaluate Transfer Terms", "Create Future Customer", "Check Credit", "Generate Agreement", "Finalize Transfer"];
return (
<div>
<div id="sidebar-title">
<div id="sc-logo">
LOGO
</div>
<div>Contract Transfer for:</div>
<div>XYZ</div>
<br />
</div>
<ul className="list-group" id="sidebar-list">
{sidebarItemNames.map(function(n, index)
{
return <SidebarItem key={index} index={index} selectedIndex={this.selectedSidebarItem} name={n} handleClick={this.handleClick(index).bind(this)} />;
})}
</ul>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return {
selectedSidebarItem: (index) => dispatch(ActionCreators.setSelectedSidebarItem(index))
}
}
const conn = connect(
null,
mapDispatchToProps
)(Sidebar)

Categories