Declare setState via function - reactjs

Unable to declare setState via the function. And I can’t understand what the mistake is.
import React, { Component } from 'react';
export default class App extends Component {
state = {
text: ' ',
};
render() {
const text = () => {
this.setState({ text: 'Good afternoon' });
};
return(
)
}
}

Functions and setState should be outside the render lifecycle.
More Info: https://reactjs.org/docs/state-and-lifecycle.html
SetState Guide: https://blog.logrocket.com/an-imperative-guide-to-setstate-in-react-b837ceaf8304/
import React, { Component } from 'react';
export default class App extends Component {
state = {
text: " "
}
const text = () => {
this.setState({text: "Good afternoon"})
}
render() {
return(
)
}
}

Please bring the function text outside the render function.
export default class App extends Component {
state = {
text: ' ',
};
//function in here
text = () => {
this.setState({ text: 'Good afternoon' });
}
render() {
return ()
}
}

Anyway, when I pass the text of the function beyond the rendering limits, setState does not work.

Related

Child component not triggering rendering when parent injects props with differnt values

Here are my components:
App component:
import logo from './logo.svg';
import {Component} from 'react';
import './App.css';
import {MonsterCardList} from './components/monster-list/monster-card-list.component'
import {Search} from './components/search/search.component'
class App extends Component
{
constructor()
{
super();
this.state = {searchText:""}
}
render()
{
console.log("repainting App component");
return (
<div className="App">
<main>
<h1 className="app-title">Monster List</h1>
<Search callback={this._searchChanged}></Search>
<MonsterCardList filter={this.state.searchText}></MonsterCardList>
</main>
</div>
);
}
_searchChanged(newText)
{
console.log("Setting state. new text: "+newText);
this.setState({searchText:newText}, () => console.log(this.state));
}
}
export default App;
Card List component:
export class MonsterCardList extends Component
{
constructor(props)
{
super(props);
this.state = {data:[]};
}
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
_loadData(monsterCardCount)
{
fetch("https://jsonplaceholder.typicode.com/users", {
method: 'GET',
}).then( response =>{
if(response.ok)
{
console.log(response.status);
response.json().then(data => {
let convertedData = data.map( ( el, index) => {
return {url:`https://robohash.org/${index}.png?size=100x100`, name:el.name, email:el.email}
});
console.log(convertedData);
this.setState({data:convertedData});
});
}
else
console.log("Error: "+response.status+" -> "+response.statusText);
/*let data = response.json().value;
*/
}).catch(e => {
console.log("Error: "+e);
});
}
render()
{
console.log("filter:" + this.props.filter);
return (
<div className="monster-card-list">
{this.state.data.map((element,index) => {
if(!this.props.filter || element.email.includes(this.props.filter))
return <MonsterCard cardData={element} key={index}></MonsterCard>;
})}
</div>
);
}
}
Card component:
import {Component} from "react"
import './monster-card.component.css'
export class MonsterCard extends Component
{
constructor(props)
{
super(props);
}
render()
{
return (
<div className="monster-card">
<img className="monster-card-img" src={this.props.cardData.url}></img>
<h3 className="monster-card-name">{this.props.cardData.name}</h3>
<h3 className="monster-card-email">{this.props.cardData.email}</h3>
</div>
);
}
}
Search component:
import {Component} from "react"
export class Search extends Component
{
_searchChangedCallback = null;
constructor(props)
{
super();
this._searchChangedCallback = props.callback;
}
render()
{
return (
<input type="search" onChange={e=>this._searchChangedCallback(e.target.value)} placeholder="Search monsters"></input>
);
}
}
The problem is that I see how the text typed in the input flows to the App component correctly and the callback is called but, when the state is changed in the _searchChanged, the MonsterCardList seems not to re-render.
I saw you are using state filter in MonsterCardList component: filter:this.props.searchText.But you only pass a prop filter (filter={this.state.searchText}) in this component. So props searchTextis undefined.
I saw you don't need to use state filter. Replace this.state.filter by this.props.filter
_loadData will get called only once when the component is mounted for the first time in below code,
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
when you set state inside the constructor means it also sets this.state.filter for once. And state does not change when searchText props change and due to that no rerendering.
constructor(props)
{
super(props);
this.state = {data:[], filter:this.props.searchText};
}
If you need to rerender when props changes, use componentDidUpdate lifecycle hook
componentDidUpdate(prevProps)
{
if (this.props.searchText !== prevProps.searchText)
{
this._loadData();
}
}
Well, in the end I found what was happening. It wasn't a react related problem but a javascript one and it was related to this not been bound to App class inside the _searchChanged function.
I we bind it like this in the constructor:
this._searchChanged = this._searchChanged.bind(this);
or we just use and arrow function:
_searchChanged = (newText) =>
{
console.log("Setting state. new text: "+newText);
this.setState({filter:newText}, () => console.log(this.state));
}
Everything works as expected.

React Speech Recognition - inserting the text to the memory by updating the state

There is a similar question but I can't comment on it so I opening a new one.
I am new to React and try to implement React SpeechRecognition component for my app. The text should be in an input box. the code for it (from react doc [https://www.npmjs.com/package/react-speech-recognition][1] - with span tag instead of an input):
import React, { PropTypes, Component } from 'react'
import SpeechRecognition from 'react-speech-recognition'
const propTypes = {
// Props injected by SpeechRecognition
transcript: PropTypes.string,
resetTranscript: PropTypes.func,
browserSupportsSpeechRecognition: PropTypes.bool
}
class Dictaphone extends Component {
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props
if (!browserSupportsSpeechRecognition) {
return null
}
return (
<div>
<button onClick={resetTranscript}>Reset</button>
<span>{transcript}</span>
</div>
)
}
}
Dictaphone.propTypes = propTypes
export default SpeechRecognition(Dictaphone)
Now I try to update a state of text (a string) by the transcript (the words that have been already recognized) but I can't make it.
from an earlier question, someone suggested this:
<input
type="text"
value={transcript}
onChange={event => this.onInputChange(event.target.value)}
/>
now when I speak, I do see the words in the input box,
so the final code should be :
import React, { Component } from "react";
import PropTypes from "prop-types";
import SpeechRecognition from "react-speech-recognition";
const propTypes = {
// Props injected by SpeechRecognition
transcript: PropTypes.string,
resetTranscript: PropTypes.func,
browserSupportsSpeechRecognition: PropTypes.bool
};
class Dictaphone extends Component {
constructor() {
super();
this.state = {
text: '',
events: []
}
}
onInputChange = (event) => {
console.log (event.target.value);
this.setState( {text: event.target.value} );
}
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props;
if (!browserSupportsSpeechRecognition) {
return null
}
return (
<div>
<button onClick={resetTranscript}>Reset</button>
<input
className='bg-light-blue'
type="text"
value={transcript}
onChange={event => this.onInputChange(event.target.value)}
/>
</div>
)
}
}
Dictaphone.propTypes = propTypes;
export default SpeechRecognition(Dictaphone);
but when I console.log(event.target.value) which is text - I see nothing so I'm doing something wrong.
Note that if I just write in the render func:
render() {
const { transcript, resetTranscript, browserSupportsSpeechRecognition } = this.props;
var x = transcript;
console.log('x is ',x);
console.log('x length is: ',x.length);
.....
it does console the transcript (x) but it's not what I want - I need to save it in text by updating the state.
any suggestion?
If you need to store the transcript prop in your state you should do something like this.
componentDidUpdate(prevProps){
if(prevProps.transcript !== this.props.transcript){
this.setState({
text: this.props.transcript
});
}
}
In your render method use this.state.text to show in the input value.
Also in your constructor do
this.state = {
text: props.transcript
}

How to access state values by index in React

I'm struggling to access values inside a state in React using axios and my code is as follows:
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
class App extends React.Component {
state = {
moviedata:null
}
getMovies(){
axios.get("http://127.0.0.1:8000/api/v1/movies/")
.then(moviedata => {
this.setState({
moviedata: moviedata.data
});
})
.then(x => { console.log(this.state.moviedata)});
}
componentDidMount(){
this.getMovies();
}
render () {
return <h1>Movie Examples include </h1>
}
}
ReactDOM.render(<App />, document.getElementById('react-app'));
The console.log looks like this:
0: {title: "Terminator 2: Judgement Day", plot: "Rise of the machines.", year: 1991}
1: {title: "The Italian Job", plot: "A comic hinging on a traffic jam", year: 1969}
How can I include the title of the first entry, i.e. 'Terminator 2: Judgement Day', inside the h1 tag, after the word 'include'?
I tried:
render () {
return <h1>Movie Examples include {this.state.moviedata[0].title}</h1>
}
and got an error TypeError: Cannot read property '0' of null
moviedata in your component state is initially null, so trying to access [0] from that will give rise to your error.
You could e.g. return early from the render method until moviedata has been set.
Example
class App extends React.Component {
// ...
render() {
const { moviedata } = this.state;
if (moviedata === null) {
return null;
}
return <h1>Movie Examples include {moviedata[0].title}</h1>;
}
}
You have to account for the fact that the Axios request is asynchronous, so the component may render before the data are loaded. For example:
render () {
const data = this.state.moviedata;
return <h1>Movie Examples include {data ? data[0].title : ""}</h1>
}
First, you have to "know" that the component is in a "loading" state. Without that, your state data is undefined (Still loading)
Here's how to do it:
class App extends React.Component {
state = {
moviedata:null,
isLoading: true
}
getMovies(){
axios.get("http://127.0.0.1:8000/api/v1/movies/")
.then(moviedata => {
this.setState({
moviedata: moviedata.data,
isLoading: false
});
})
.then(x => { console.log(this.state.moviedata)});
}
componentDidMount(){
this.getMovies();
}
render () {
if (this.state.isLoading) {
return <h1>Please wait...</h1>
}
// show only the first movie
return <h1>Movie #1 {this.state.moviedata[0].title}</h1>;
// show all the movies
return (
<>
{this.state.moviedata.map((m, idx) => <h1 key={idx}>Movie: {m.title}</h1>}
</>);
}
}

Reactjs. Counter of renders

How to make counter of renders the child component in parent?
I have 2 components Widget (parent) and Message(child). I passed counter from child to parent and trying to set getting value from child set to state. And I getting err: Maximum update depth exceeded.
There is child component Message:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.changeCount = this.changeCount.bind(this);
this.state = { h: 0, counter: 0 };
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
changeCount = () => {
this.setState(state => ({
counter: ++state.counter
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.changeCount();
this.props.getCount(this.state.counter);
}
render() {
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
There is parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: state.counter
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
What I do wrong?
The answer by #Yossi counts total renders of all component instances. This solution counts how many renderes and re-renders an individual component has done.
For counting component instance renders
import { useRef } from "react";
export const Counter = props => {
const renderCounter = useRef(0);
renderCounter.current = renderCounter.current + 1;
return <h1>Renders: {renderCounter.current}, {props.message}</h1>;
};
export default class Message extends React.Component {
constructor() {
this.counter = 0;
}
render() {
this.counter++;
........
}
}
In order to count the number of renders, I am adding a static variable to all my components, and incrementing it within render().
For Class components:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export class SampleClass extends Component {
render() {
if (__DEV__) {
renderCount += 1;
console.log(`${this.constructor.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
}
For functional Components:
import React from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export function SampleFunctional() {
if (__DEV__) {
renderCount += 1;
console.log(`${SampleFunctional.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
The componentDidUpdate is calling this.changeCount() which calls this.setState() everytime after the component updated, which ofcourse runs infinitely and throws the error.
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
// Add a if-clause here if you really want to call `this.changeCount()` here
// For example: (I used Lodash here to compare, you might need to import it)
if (!_.isEqual(prevProps.color, this.props.color) {
this.changeCount();
}
this.props.getCount(this.state.counter);
}

trying to pass my arrays (props) into my publish function as selector

import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import React, {Component} from 'react';
import {check} from 'meteor/check';
export const Adressen = new Mongo.Collection('Phonebook');
if (Meteor.isServer) {
Meteor.publish('ArrayToExport', function(branches) {
check(branches, [Match.Any]);
if(branches.length > 10){
return this.ready()
};
return Adressen.find(
{branche: {$in: branches}}, {fields: {firmenname:1, plz:1}}
);
});
}
.
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
class ExportArray extends Component{
constructor(props){
super(props);
this.state = {
branches: this.props.filteredBranches
};
}
render(){
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
this.props.filteredBranche is a pure array,generated through controlled input field. this.props.filteredBranches changes as Input changes, in parent Component.
I thought I was sending my this.props.filteredBranches as an argument through withTracker function. But nothing is passed to the publish function.
if (Meteor.isServer) {
arrayExfct = function (array){
return {
find: {branche:{$in: array }},
fields: {firmenname:1, plz:1}
};
}
Meteor.publish('ArrayToExport', function (array) {
return Adressen.find(
arrayExfct(array).find, arrayExfct(array).fields);
});
}
.
export default withTracker( () => {
arrayExfct = function(array) {
return {
find: {branche: {$in: array}},
fields: {firmenname:1, plz:1}
}
}
var array = ['10555'];
Meteor.subscribe('ArrayToExport', array );
var arrayExfct = Adressen.find(arrayExfct(array).find, arrayExfct(array).fields);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
It would help if you also added an example of where you used this component and how you pass props to it, but I think I see your problem.
You expect the local state in your rendering component to get into the withTracker container, but that would be the other way around. When you make the withTracker container, you are really making another react component that renders your display component (ExportArray) and passes the data (ArrayToExport) down into it.
So, props go like this currently:
external render -> withTracker component -> ExportArray
What you need to do it to get the filteredBranches (which you pass from a parent component?) from the props argument in withTracker and pass that to the subscribtion,
class ExportArray extends Component{
exportArrays () {
const { ArrayToExport } = this.props;
}
render(){
const { ArrayToExport } = this.props;
return(
<div>
<button onClick={this.exportArrays}></button>+
</div>
);
}
}
export default withTracker(propsFromParent => {
const { filteredBranches } = propsFromParent;
Meteor.subscribe('ArrayToExport', filteredBranches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Hi the issue is with the code below. The parameter called branches is the props so branches.branches is the array you passed in.
export default withTracker( (branches) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Try the following.
export default withTracker( ({branches}) => {
Meteor.subscribe('ArrayToExport', branches);
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
Notice all that changed was
(branches)
became
({branches})
I solved my problem with a combination of Session Variables and State.
//Client
import React, { Component } from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import {Adressen} from "../api/MongoDB";
import {Meteor} from 'meteor/meteor';
import { Session } from 'meteor/session';
class ExportArray extends Component{
constructor(){
super();
this.state = {
x: [],
y: []
};
this.exportArrays = this.exportArrays.bind(this);
}
exportArrays(e){
e.preventDefault();
this.setState({x: this.props.filteredBranches});
this.setState({y: this.props.filteredPostleitzahlen});
}
render(){
var selector = {branche: {$in: this.state.x},plz: {$in: this.state.y}};
Session.set('selector', selector);
return(
<div>
<button onClick={this.exportArrays}> Commit </button>
</div>
);
}
}
export default withTracker( () => {
const ArrayfürExport = Meteor.subscribe('ArrayToExport', Session.get('selector') );
return {
ArrayToExport: Adressen.find({}).fetch()
};
})(ExportArray);
//Server
Meteor.publish('ArrayToExport', function (selector) {
console.log('von mongodb', selector);
return Adressen.find(
selector
, {
fields: {firmenname:1, plz:1}
});
});
}

Resources