I am new to React and trying to use React Hooks but I am getting error.
I am not able to figure out which line of code causing infinite loop as in the console it says
Too many re-renders. React limits the number of renders to prevent an infinite loop
The above error occurred in the <ProcessingInvoiceScreen> component:
in ProcessingInvoiceScreen (at CreateInvoiceBySkid.js:49)
and from line 49 is the const ProcessingInvoiceScreen = () => {
import React, { Component,useState } from 'react';
import {connect} from "react-redux";
import * as actions from "../../../../actions";
class CreateInvoiceBySkid extends Component{
constructor(props){
super(props)
}
async componentDidMount(){
if (!this.props.authenticated && (this.props.authenticated.role !== "superAdmin" || this.props.authenticated.role === "admin" || this.props.authenticated.role === "operator")) {
this.props.history.push('/');
} else {
const user = this.props.authenticated;
this.props.getProcessingInvoiceSkids(user);
this.props.getProducts(user);
}
}
render(){
const ProcessingInvoiceScreen = () => {
const [closedSkids, setclosedSkids] = useState({});
const [selectedSkids, setSelectedSkids] = useState([]);
if(this.props.processingInvoiceSkids){
setclosedSkids(this.props.processingInvoiceSkids.rows);
}
console.log(this.props.processingInvoiceSkids);
return(
<div>
Hello World
</div>
)
}
return(
<div>
<ProcessingInvoiceScreen/>
</div>
)
}
}
const mapStateToProps=(state)=>{
return {
authenticated:state.authenticated,
errorMessage: state.errorMessage,
users: state.users,
processingInvoiceSkids: state.processingInvoiceSkids,
products: state.products
}
};
export default connect(mapStateToProps,actions)(CreateInvoiceBySkid);
What am I doing here that causes infinite loop?
This line is causing the error. Every time the component is renderer, the setclosedSkids function is called and the component is re-renderer again and again :
if(this.props.processingInvoiceSkids){
setclosedSkids(this.props.processingInvoiceSkids.rows);
}
Move this block to an useEffect callback function:
const ProcessingInvoiceScreen = ({processingInvoiceSkids}) => {
const [closedSkids, setclosedSkids] = useState({});
const [selectedSkids, setSelectedSkids] = useState([]);
useEffect(()=> {
if(processingInvoiceSkids){
setclosedSkids(processingInvoiceSkids.rows);
}
}, [processingInvoiceSkids.rows])
return(
<div>
Hello World
</div>
)
}
I recommend to move the function out of the parent component. Then in parent component:
return(
<div>
<ProcessingInvoiceScreen processingInvoiceSkid={this.processingInvoiceSkid}/>
</div>
)
Related
In my unit test, I want to click an element in my wrapper component that affects the child component. The queryByTestId works before the await waitFor call, but the 2nd queryByTestID returns "null". I'm trying to test what happens in the child component when the language changes.
In my test I have the following:
const { queryByTestId, container } = render(
<TestIntlWrapper>
<MyComponent />
</TestIntlWrapper>
);
expect(queryByTestId("test-intl-wrapper")).toBeInTheDocument;
await waitFor(() => expect(mockedAxios.get).toBeCalledTimes(expectedNumOfAPICalls));
expect(mockedAxios.get).toBeCalledWith(expectedURL1);
expect(mockedAxios.get.mock.calls[1][0]).toBe(expectedURL2);
expect(mockedAxios.get.mock.calls[thirdCall][0]).toBe(expectedURL3);
expect(queryByTestId("test-intl-wrapper")).toBeInTheDocument; //queryByTestId returns null here
TestIntlWrapper.tsx
import React, { useEffect, useState } from "react";
import { IntlProvider } from "react-intl";
interface TestIntlWrapperProps {
children: JSX.Element
}
export default function TestIntlWrapper({children}: TestIntlWrapperProps) {
const languages = ["en", "es", "fr"]
const [currentLanguage, setCurrentLanguage] = useState(languages[0]);
const [clickCount, setClickCount] = useState(0);
const setClick = () => {
setClickCount(clickCount + 1)
}
useEffect(() => {
setCurrentLanguage(languages[clickCount % languages.length]);
},[clickCount] )
return (
<div data-testid="test-intl-wrapper" onClick={setClick}>
<IntlProvider locale={currentLanguage}>
{children}
</IntlProvider>
</div>
)
}
Any help is appreciated
The issue was the application was throwing an uncaught error in the waitFor which is why it was running an empty div and the data-testid was disappearing.
I am using Draft.js to create Rich Text Editor.
This is Parent:
import React from 'react'
import RichTextEditor from './RichTextEditor'
import {useEffect, useState, useCallback} from "react"
import { EditorState, convertToRaw } from 'draft-js';
function AddPage() {
// MY STATE
const [editorState, setEditorState] = useState({editorState: EditorState.createEmpty()});
// MY CALLBACKS ATTEMPTS
function onChangeCallback(editorState) {
const contentState = editorState.getCurrentContent();
console.log(convertToRaw(contentState))
setEditorState({editorState});
}
// const onChangeCallback = useCallback((editorState) => {
// const contentState = editorState.getCurrentContent();
// console.log(convertToRaw(contentState))
// setEditorState({editorState});
// }, []);
// const onChangeCallback = ({editorState}) => {
// setEditorState({editorState});
// }
return (
<div>
<div className="app__body">
{editorState && <RichTextEditor editorState={editorState} onChangeCallback={onChangeCallback} /> }
</div>
</div>
)
}
export default AddPage
And this is Child (I made it as short as I could)
import React from 'react'
import { Editor, RichUtils, getDefaultKeyBinding } from 'draft-js';
import './RichTextEditor.css'
import 'draft-js/dist/Draft.css';
class RichTextEditor extends React.Component {
constructor(props) {
super(props);
this.state = this.props.editorState; // <----- PASSED STATE
this.onChange = this.props.onChangeCallback; //<----- PASSED CALLBACK
this.focus = () => this.refs.editor.focus();
this.handleKeyCommand = this._handleKeyCommand.bind(this);
this.mapKeyToEditorCommand = this._mapKeyToEditorCommand.bind(this);
this.toggleBlockType = this._toggleBlockType.bind(this);
this.toggleInlineStyle = this._toggleInlineStyle.bind(this);
}
_handleKeyCommand(command, editorState) {
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
this.onChange(newState);
return true;
}
return false;
}
_mapKeyToEditorCommand(e) {
if (e.keyCode === 9 /* TAB */) {
const newEditorState = RichUtils.onTab(
e,
this.state.editorState,
4, /* maxDepth */
);
if (newEditorState !== this.state.editorState) {
this.onChange(newEditorState);
}
return;
}
return getDefaultKeyBinding(e);
}
_toggleBlockType(blockType) {
this.onChange(
RichUtils.toggleBlockType(
this.state.editorState,
blockType
)
);
}
_toggleInlineStyle(inlineStyle) {
this.onChange(
RichUtils.toggleInlineStyle(
this.state.editorState,
inlineStyle
)
);
}
render() {
let className = 'RichEditor-editor';
var contentState = this.state.editorState.getCurrentContent();
if (!contentState.hasText()) {
if (contentState.getBlockMap().first().getType() !== 'unstyled') {
className += ' RichEditor-hidePlaceholder';
}
}
return (
<div className="RichEditor-root">
<button onClick={this.onChange}>aaa</button>
<div className={className} onClick={this.focus}>
<Editor
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
keyBindingFn={this.mapKeyToEditorCommand}
onChange={this.onChange}
placeholder="Add your text here"
ref="editor"
spellCheck={true}
/>
</div>
</div>
);
}
}
export default RichTextEditor
My problem is that when I click a key, editorState in Parent callback contains the letter, but when I click again, it looks like editorState is a new object with one, the last clicked letter. I never see anything in the editor.
editorState is the object that should contain a lot of blocks, each corresponding to another line of text. Instead, this is console output:
BTW. I need to store editorState in Parent. When I move more or less the same useState code to the child, it behaves the way it should.
It seems that it was a beginner question hidden in Draft.js. I did not understand basic hooks' functionality. Each time I run callback, setEditorState re-renders the component. To re-render it with a memoized version of the callback, and it’ll only be changed if one of the dependencies has changed in callback (see last line):
const onChangeCallback = useCallback((editorState) => {
const contentState = editorState.getCurrentContent();
console.log(convertToRaw(contentState))
setEditorState({editorState});
}, [editorState]);
This helped me
How to prevent re-rendering of components that have not changed?
I would like to use react player library in my app
import React, { useEffect, useRef } from "react";
import ReactPlayer from "react-player";
import { useSelector } from "../../../redux/useSelector";
const VIDEO_URL =
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
export const Player: React.FC = React.memo(() => {
const player = React.useRef<any>(null);
const targetTimestamp = useSelector((state) => {
const {
timestamps: { selectedTimestamp },
} = state;
return (selectedTimestamp && selectedTimestamp.timestamp) || 0;
});
useEffect(() => {
console.log(player.current);
player.current && player.current.seekTo(targetTimestamp );
}, [targetTimestamp]);
return (
<ReactPlayer
ref={player}
url={VIDEO_URL}
controls
width={1280}
height={720}
/>
);
});
console.log(player.current); works, but on the next line I get the error
Uncaught TypeError: Cannot read property 'seekTo' of undefined
What's wrong? I can't use useRef here? Should I make Player class component? How to fix it and make it work?
////
let player;
const ref = (playerRef) => {
player = playerRef;
}
////
const seekHandler = (event) => {
....
player.seekTo(parseFloat(event.target.value), "fraction");
...
};
...
<ReactPlayer
ref={ref}
.....
I'm trying to make a timer in my App with React + Redux.
So I have a component parent:
import React, { Component } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import QuestionCounter from "../question-counter";
import FinishButton from "../finish-button";
import TimeCounter from "../time-counter";
import PauseButton from "../pause-button";
import testFinished from "../../actions/test-finished";
import timerTick from "../../actions/timer-tick";
import setTimer from "../../actions/set-timer";
import totalWithEwStruct from "../hoc/total-with-ew-structure";
import withIndicators from "../hoc/with-indicators";
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
const mapStateToProps = ({ total, loading, error }) => {
return { total, loading, error };
};
const mapDispatchToProps = {
testFinished,
setTimer,
timerTick
}
export default compose(
totalWithEwStruct(),
connect(mapStateToProps, mapDispatchToProps),
withIndicators()
)(Total);
I try use timerTick by timer in componentDidMount
import React, { Component } from "react";
export default class TimeCounter extends Component {
componentDidMount() {
const { setTimer, timerTick } = this.props;
let timer = setInterval(() => {
timerTick();
console.log("tick");
}, 1000);
setTimer(timer);
}
componentDidUpdate() {
const { timeLeft, testFinished } = this.props;
if (timeLeft <= 0) {
testFinished();
}
}
render() {
const { timeLeft } = this.props;
return (
<div className="question-counter__timeleft">
Времени осталось
<span className="question-counter__timer">{timeLeft}</span>
</div>
);
}
}
So I see "tick" - "tick" - "tick" in console, but React doesn't dispatch my timerTick() function to reducer.
I have tried log to console action.type for debugging, and there is no action of timerTick.
const timerTick = () => {
return {
type: "TIMER_TICK"
};
};
export default timerTick;
Its code of action.
I don't understand why it doesn't work.
Your Total component needs to take timerTick function from props which is the one that is linked with redux store as you have added it to mapDispatchToProps.
If you do not destructure it from props, the ccomponent will use the imported function which isn't an action created unless its passed to dispatch function
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft, timerTick } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
You need to add dispatch of timer tick inside timer tick component. Because child component not aware about the actions.
Please refer below link for more details:
https://itnext.io/dispatching-actions-from-child-components-bd292a51f176
Response
if your component is not connected to redux you won’t be able to dispatch any action.
What do I mean?
Example
import React from “react”;
import { connect } from “react-redux”;
class MyCom extensa React.Component {
componentDidMount () {
const { action } = this.props;
action();
}
render () {
.....
}
}
const toState = state => ({....});
const toDispatch = {
action
};
export default connect(toState, toDispatch)(MyCom);
Explains
Basically connect from ”react-redux” is a HOC a high order component that on javascript world: is none but a high order function. a function that return another function.
I'm a bit new to React and Firestore and already trying to figure out what is happening for a couple of hours. I Try to make my filter function working with data which I receive from Firestore in APP.js. I pass the data {tasks, searchTerm} to DASHBOARD component. The filter worked before when using state and props, but after replacing the hard-coded data in state with firestore data, it doesn't work anymore and I get the following error when filtering the array in the DASHBOARD component:
Cannot read property 'toLowerCase' of undefined
I've tried to send the data without any filtering directly to TASKS.js and this is working correctly (all the tasks are shown). But as soon as I pass newArray to , it doesn't work anymore.
Also, when logging task.title in tasks.filter function in the DASHBOARD component, it shows all the data (with a little delay because the data is coming from Firestore)
APP.JS -
import React, { Component } from 'react';
import './App.css';
import Dashboard from './Components/Dashboard/Dashboard'
import AddTask from './Components/Tasks/Task/AddTask'
import Navbar from './Components/Navbar/Navbar'
import Searchbar from './Components/Searchbar/Searchbar'
import firebase from './Firebase';
class App extends Component {
constructor(props) {
super(props)
this.ref = firebase.firestore().collection('tasks')
this.state = {
tasks: [],
searchTerm: ""
}
this.handleLikeButton = this.handleLikeButton.bind(this)
this.handleRemoveButton = this.handleRemoveButton.bind(this)
this.addTask = this.addTask.bind(this)
this.handleFilter = this.handleFilter.bind(this)
}
componentWillMount() {
const db = firebase.firestore()
const allTasks = []
db.collection('tasks').onSnapshot(collection => {
const tasks = collection .docs.map(doc => doc.data())
this.setState({ tasks: tasks, searchTerm: "" })
})
}
handleLikeButton = (task) => (e) => {
const tasks = [...this.state.tasks]
const index = tasks.indexOf(task)
tasks[index].likes++
this.setState({
tasks: tasks
})
}
addTask = (taskName) => (e) => {
this.ref.add({
id: Math.floor(Math.random() * 100000000000000),
title: taskName,
likes: 0
})
}
handleRemoveButton = (removingTask) => (e) => {
const tasks = [...this.state.tasks]
const newTasks = tasks.filter(task => removingTask.id !== task.id)
this.setState({
tasks: newTasks
})
}
handleFilter = (searchTerm) => {
this.setState({
searchTerm: searchTerm
})
}
render() {
return (
<div className="App">
<Navbar />
<Searchbar handleFilter={this.handleFilter} />
<AddTask addTask={this.addTask} />
<Dashboard tasks={this.state.tasks} searchTerm={this.state.searchTerm} handleLikeButton={this.handleLikeButton} handleRemoveButton={this.handleRemoveButton}/>
</div>
);
}
}
export default App;
DASHBOARD.JS -
import React, { Component } from 'react'
import Tasks from '../Tasks/Tasks'
class Dashboard extends Component {
constructor(props) {
super(props)
this.filterTasks = this.filterTasks.bind(this)
}
filterTasks = () => {
const tasks = [...this.props.tasks]
const newArray = tasks.filter(task =>
task.title.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) > -1)
return (
<Tasks tasks={newArray} handleLikeButton={this.props.handleLikeButton} handleRemoveButton={this.props.handleRemoveButton} />
)
}
render() {
return (
<div>
<h2>Dashboard</h2>
{this.filterTasks()}
</div>
)
}
}
export default Dashboard
ADDTASK.JS
import React, { Component } from 'react'
class AddTask extends Component {
constructor(props) {
super(props)
this.state = {
addNewTaskFieldEmpty: true,
taskName: ""
}
this.onChangeHandler = this.onChangeHandler.bind(this)
this.disableButton = this.disableButton.bind(this)
}
onChangeHandler(e) {
this.setState({
taskName: e.target.value,
})
this.disableButton(e.target.value)
}
disableButton(taskName) {
if(taskName.length == 0) {
this.setState({addNewTaskFieldEmpty: true})
} else {
this.setState({addNewTaskFieldEmpty: false})
}
}
render() {
return (
<div>
<div className="mdc-text-field half-size">
<input className="mdc-text-field__input " onChange={this.onChangeHandler} />
<div className="mdc-line-ripple"></div>
<label className="mdc-floating-label">Task Name</label>
</div>
<a className={"btn-floating btn-large waves-effect waves-light red " + (this.state.addNewTaskFieldEmpty ? 'disabled' : '')} onClick={this.props.addTask(this.state.taskName)}><i className="material-icons">add</i></a>
</div>
)
}
}
export default AddTask
Lint your App.css for any errors.
I encountered this message. I traced it to a CSS include:
.box-table { border-color:; border: 1px solid #dbdad8; }
The missing value of border-color: caused npm run build to fail.
Interestingly, the same file contained
.submenu-button.submenu-opened:after { background:; }
which caused no problems at all.