I'm trying to apply conditional rendering of EVERY component. The idea is that this hook checks for specific property name passed to component and if this property is present and has a value, it must pass certain conditions to be rendered, if property is missing it is rendered as usual. So internal components of libraries wouldn't be hit by this hook because they are missing that property. I know that I can encapsulate every component to wrapper containing this logic, but it means complete rewriting and alot of extra code.
Purpose: rendering components based on user rights.
Example of what I would like to accomplish with use of undocumented(fictitious) code.
const useBeforeComponentRender = (props, next) => {
// next() is functional Component
const { userRightsArray } = AppContext;
const requiredRights = props.renderWithRights;
if (Array.isArray(requiredRights)) {
if (requiredRights.some(r => userRightsArray.includes(r))) {
return next();
} else {
return null;
}
}
return next();
}
React.hook({useBeforeComponentRender});
function ActionBox() {
return (
<EditButton renderWithRights=['edit', 'superAdmin']>
)
}
Any advice(even different approach) will be appreciated.
I was trying to fetch an array of objects when I encounter this problem. Sorry for my messy code, I'm a beginner.
export class App extends Component {
state ={
character:[]
}
componentDidMount(){
fetch('https://swapi.dev/api/people/').then(data => data.json()).then(res =>{
this.setState(() =>{
const ar = res.results
return {
character: ar
}
})
})
}
render() {
return (
<div>
test
{console.log(this.state.character[0])}
</div>
)
}
}
That code works fine for me at first until I changed the console log to {console.log(this.state.character[0].name)} it says "cannot read property of an undefined" even though the first line displays the object perfectly
Your App component fetches the data in componentDidMount life cycle method which will execute after the first mount. {console.log(this.state.character[0].name)} shows error "cannot read property of undefined because when the component mounts for first time, the data is not available yet, which means this.state.character is still an empty array. So when try to access this.state.character[0].name, its like saying give me the value of undefined.name which will give you an error because name property does not exist on undefined. To fix this issue you can check if character.length in a conditional and then try to access .name.
Example
render() {
return (
<div>
test
{this.state.character.length && <p>{this.state.character[0].name}</p>}
</div>
)
}
Fetch is asynchronous so you will take some time to get the result but before that your jsx is called already. So it is good to use conditional jsx. Please check the complete example as below where I shown all the name of character array under li element.
export class App extends React.Component {
state = {
character: []
};
componentDidMount() {
fetch('https://swapi.dev/api/people/').then(data => data.json()).then(res => {
this.setState(() => {
const ar = res.results;
return {
character: ar
}
})
})
}
render() {
return (
<div>
test
{
this.state.character && this.state.character.length && this.state.character.map((item, index) => {
return <li key={index}>{item.name}</li>
})
}
</div>
)
}
}
export default App;
So that will not work. Data is not there yet. There are bunch of ways to workaround it. Most simple one is to have object structure predefined in the state object, which is not very dynamic. Otherwise simple .length + if + else will do the job.
I am using Redux with Class Components in React. Having the below two states in Redux store.
{ spinner: false, refresh: false }
In Parent Components, I have a dispatch function to change this states.
class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
this.props.onShowSpinner();
this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
In Child Component, I am trying to reload the parent component like below.
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
if (somecondition) {
// doing some redux store update
props.reloadApp();
}
}
render() {
return <button />;
}
}
I am getting error as below.
Warning: Cannot update a component from inside the function body of a
different component.
How to remove this warning? What I am doing wrong here?
For me I was dispatching to my redux store in a React Hook. I had to dispatch in a useEffect to properly sync with the React render cycle:
export const useOrderbookSubscription = marketId => {
const { data, error, loading } = useSubscription(ORDERBOOK_SUBSCRIPTION, {
variables: {
marketId,
},
})
const formattedData = useMemo(() => {
// DISPATCHING HERE CAUSED THE WARNING
}, [data])
// DISPATCHING HERE CAUSED THE WARNING TOO
// Note: Dispatching to the store has to be done in a useEffect so that React
// can sync the update with the render cycle otherwise it causes the message:
// `Warning: Cannot update a component from inside the function body of a different component.`
useEffect(() => {
orderbookStore.dispatch(setOrderbookData(formattedData))
}, [formattedData])
return { data: formattedData, error, loading }
}
If your code calls a function in a parent component upon a condition being met like this:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
Try wrapping the condition in a useEffect:
const ListOfUsersComponent = ({ handleNoUsersLoaded }) => {
const { data, loading, error } = useQuery(QUERY);
useEffect(() => {
if (data && data.users.length === 0) {
return handleNoUsersLoaded();
}
}, [data, handleNoUsersLoaded]);
return (
<div>
<p>Users are loaded.</p>
</div>
);
};
It seems that you have latest build of React#16.13.x. You can find more details about it here. It is specified that you should not setState of another component from other component.
from the docs:
It is supported to call setState during render, but only for the same component. If you call setState during a render on a different component, you will now see a warning:
Warning: Cannot update a component from inside the function body of a different component.
This warning will help you find application bugs caused by unintentional state changes. In the rare case that you intentionally want to change the state of another component as a result of rendering, you can wrap the setState call into useEffect.
Coming to the actual question.
I think there is no need of getDerivedStateFromProps in the child component body. If you want to trigger the bound event. Then you can call it via the onClick of the Child component as i can see it is a <button/>.
class Child extends React.Component {
constructor(props){
super(props);
this.updateState = this.updateState.bind(this);
}
updateState() { // call this onClick to trigger the update
if (somecondition) {
// doing some redux store update
this.props.reloadApp();
}
}
render() {
return <button onClick={this.updateState} />;
}
}
Same error but different scenario
tl;dr wrapping state update in setTimeout fixes it.
This scenarios was causing the issue which IMO is a valid use case.
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setSomeState(someNewValue);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
fix
const [someState, setSomeState] = useState(someValue);
const doUpdate = useRef((someNewValue) => {
setTimeout(() => {
setSomeState(someNewValue);
}, 0);
}).current;
return (
<SomeComponent onSomeUpdate={doUpdate} />
);
In my case I had missed the arrow function ()=>{}
Instead of onDismiss={()=>{/*do something*/}}
I had it as onDismiss={/*do something*/}
I had same issue after upgrading react and react native, i just solved that issue by putting my props.navigation.setOptions to in useEffect. If someone is facing same problen that i had i just want to suggest him put your state changing or whatever inside useEffect
Commented some lines of code, but this issue is solvable :) This warnings occur because you are synchronously calling reloadApp inside other class, defer the call to componentDidMount().
import React from "react";
export default class App extends React.Component {
reloadHandler = () => {
console.log("[App] reloadComponent");
// this.props.onShowSpinner();
// this.props.onRefresh();
};
render() {
return <Child reloadApp={this.reloadHandler} />;
}
}
class Child extends React.Component {
static getDerivedStateFromProps(props, state) {
// if (somecondition) {
// doing some redux store update
props.reloadApp();
// }
}
componentDidMount(props) {
if (props) {
props.reloadApp();
}
}
render() {
return <h1>This is a child.</h1>;
}
}
I got this error using redux to hold swiperIndex with react-native-swiper
Fixed it by putting changeSwiperIndex into a timeout
I got the following for a react native project while calling navigation between screens.
Warning: Cannot update a component from inside the function body of a different component.
I thought it was because I was using TouchableOpacity. This is not an issue of using Pressable, Button, or TouchableOpacity. When I got the error message my code for calling the ChatRoom screen from the home screen was the following:
const HomeScreen = ({navigation}) => {
return (<View> <Button title = {'Chats'} onPress = { navigation.navigate('ChatRoom')} <View>) }
The resulting behavior was that the code gave out that warning and I couldn't go back to the previous HomeScreen and reuse the button to navigate to the ChatRoom. The solution to that was doing the onPress in an inline anonymous function.
onPress{ () => navigation.navigate('ChatRoom')}
instead of the previous
onPress{ navigation.navigate('ChatRoom')}
so now as expected behavior, I can go from Home to ChatRoom and back again with a reusable button.
PS: 1st answer ever in StackOverflow. Still learning community etiquette. Let me know what I can improve in answering better. Thanx
If you want to invoke some function passed as props automatically from child component then best place is componentDidMount lifecycle methods in case of class components or useEffect hooks in case of functional components as at this point component is fully created and also mounted.
I was running into this problem writing a filter component with a few text boxes that allows the user to limit the items in a list within another component. I was tracking my filtered items in Redux state. This solution is essentially that of #Rajnikant; with some sample code.
I received the warning because of following. Note the props.setFilteredItems in the render function.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- Danger! Updates Redux during a render!
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I ran this code with React 16.12.0, I received the warning listed in the topic of this thread in my browser console. Based on the stack trace, the offending line was my props.setFilteredItems invocation within the render function. So I simply enclosed the filter invocations and state change in a useEffect as below.
import {setFilteredItems} from './myActions';
const myFilters = props => {
const [nameFilter, setNameFilter] = useState('');
const [cityFilter, setCityFilter] = useState('');
useEffect(() => {
const filterName = record => record.name.startsWith(nameFilter);
const filterCity = record => record.city.startsWith(cityFilter);
const selectedRecords = props.records.filter(rec => filterName(rec) && filterCity(rec));
props.setFilteredItems(selectedRecords); // <-- OK now; effect runs outside of render.
}, [nameFilter, cityFilter]);
return <div>
<input type="text" value={nameFilter} onChange={e => setNameFilter(e.target.value)} />
<input type="text" value={cityFilter} onChange={e => setCityFilter(e.target.value)} />
</div>
};
const mapStateToProps = state => ({
records: state.stuff.items,
filteredItems: state.stuff.filteredItems
});
const mapDispatchToProps = { setFilteredItems };
export default connect(mapStateToProps, mapDispatchToProps)(myFilters);
When I first added the useEffect I blew the top off the stack since every invocation of useEffect caused state change. I had to add an array of skipping effects so that the effect only ran when the filter fields themselves changed.
I suggest looking at video below. As the warning in the OP's question suggests, there's a change detection issue with the parent (Parent) attempting to update one child's (Child 2) attribute prematurely as the result of another sibling child's (Child 1) callback to the parent. For me, Child 2 was prematurely/incorrectly calling the passed in Parent callback thus throwing the warning.
Note, this commuincation workflow is only an option. I personally prefer exchange and update of data between components via a shared Redux store. However, sometimes it's overkill. The video suggests a clean alternative where the children are 'dumb' and only converse via props mand callbacks.
Also note, If the callback is invoked on an Child 1 'event' like a button click it'll work since, by then, the children have been updated. No need for timeouts, useEffects, etc. UseState will suffice for this narrow scenario.
Here's the link (thanks Masoud):
https://www.youtube.com/watch?v=Qf68sssXPtM
In react native, if you change the state yourself in the code using a hot-reload I found out I get this error, but using a button to change the state made the error go away.
However wrapping my useEffect content in a :
setTimeout(() => {
//....
}, 0);
Worked even for hot-reloading but I don't want a stupid setTimeout for no reason so I removed it and found out changing it via code works just fine!
I was updating state in multiple child components simultaneously which was causing unexpected behavior. replacing useState with useRef hook worked for me.
Try to use setTimeout,when I call props.showNotification without setTimeout, this error appear, maybe everything run inTime in life circle, UI cannot update.
const showNotifyTimeout = setTimeout(() => {
this.props.showNotification();
clearTimeout(showNotifyTimeout);
}, 100);
I am working to build a Pokedex from JSON data in React. I am refactoring this project from one I built in jQuery, so it could be that the jQuery approach is causing me to misunderstand how to approach this problem with proper React thinking. What's tripping me up so far is how to dynamically render multiple child elements based on the JSON I pass from a the parent element (this would be jQuery append).
Here is my App.js code:
class App extends Component {
render() {
return (
<div className="App background">
<div className="content">
<Header />
<TilesContainer pokedexName="national"/>
</div>
</div>
);
}
The TilesContainer essentially receives the name of a Pokedex and makes a call to an API. The individual Pokemon names are stored in an array in the TilesContainer state (this.state.pokemon), as below.
class TilesContainer extends Component {
constructor(props){
super(props);
this.state = {pokemon: []};
this.getPokemon = this.getPokemon.bind(this);
this.tiles = this.tiles.bind(this);
}
getPokemon() {
// set this.state.pokemon to the list
let link = 'https://pokeapi.co/api/v2/pokedex/' + this.props.pokedexName + '/';
fetch(link)
.then(response => response.json())
.then(myJson => {
let list = myJson['pokemon_entries'];
list.forEach(pokemon => {
this.state.pokemon.push(pokemon);
})
})
this.tiles();
}
tiles() {
if (this.state.pokemon.length > 0) {
return (
this.state.pokemon.map(pokemon => {
<Tile number={pokemon.entry_number}/>
})
)
}
}
render(){
this.getPokemon();
return (
<div id="tiles-container"
className="tiles-container">
<h1>TilesContainer Test</h1>
<Tile number={1} />
</div>
)
}
}
export default TilesContainer
Again, the idea is that a Pokemon tile is render for each Pokemon in the Pokedex JSON (which for now I've stored in this.state.pokemon - not sure if this is the best approach). I found an example here on Stack Overflow that uses an additional function (this this case this.tiles() to generate what I think is an array of returns with different child elements). The <Tile number={1} /> is a hardcoded example of how the tile is called.
Currently no dynamically-rendered tiles show up when the code runs. Is this the correct approach. I'd really appreciate any suggestions.
Thanks!
It looks like you're almost there.
First off, never modify state directly. Use this.setState() instead. State in React is updated asynchronously. For your purposes, you should be able to modify getPokemon() like the following. I also removed the this.tiles() call, as it is unnecessary.
getPokemon() {
// set this.state.pokemon to the list
let link = 'https://pokeapi.co/api/v2/pokedex/' + this.props.pokedexName + '/';
fetch(link)
.then(response => response.json())
.then(myJson => {
let list = myJson['pokemon_entries'];
this.setState({
pokemon: list,
});
})
}
A minor correction for tiles(): when using an arrow function and returning something in one line, use parentheses instead of curly braces. When you use curly braces, you have to include a return statement. With parentheses, you do not.
tiles() {
if (this.state.pokemon.length > 0) {
return (
this.state.pokemon.map(pokemon => (
<Tile number={pokemon.entry_number}/>
))
)
}
}
Next, since tiles() returns your dynamic tile components, it needs to be included in what you return in render().
render(){
return (
<div id="tiles-container"
className="tiles-container"
>
<h1>TilesContainer Test</h1>
{this.tiles()}
</div>
)
}
Lastly, I think the call to this.getPokemon() would make more sense in the constructor, rather than in render().
I think your method of getting the json data and storing it in state is fine, by the way. In the future, you may want to look into Redux to manage your state, but it could be overkill for a really small application.
so you are passing the pokedexName from the parent component which is app.js, once you get the props you can call the rest api call on the componentWillMount life cycle.
so on the render since the api call has been initiated it wont have any data thats why we are using a ternary operator to check the array once the api call get finished and we get the data we are setting the data to the pokemon array.
Since the state is updated react will automatically render a re render so the data will appear.
i hope the below code will solve the issue, please let me know :)
// App.js
import React, { Component } from 'react';
import TilesContainer from './components/TileContainer/TilesContainer'
class App extends Component {
render() {
return (
<div>
<TilesContainer pokedexName="national" />
</div>
);
}
}
export default App;
// Tiles container
import React, {Component} from 'react';
import axios from 'axios';
class TilesContainer extends Component{
//state
state ={
pokemon: []
}
// life cycle methods
componentWillMount(){
let link = 'https://pokeapi.co/api/v2/pokedex/' + this.props.pokedexName + '/';
axios.get(link)
.then(res => {
this.setState({
pokemon: res.data["pokemon_entries"]
})
})
}
render(){
let style ={display:"inline"}
return(
<div>
{
this.state.pokemon.length > 0 ?
this.state.pokemon.map(pokemon => {
return(
<div key={pokemon.entry_number}>
<p style={style}>{pokemon.entry_number}</p>
<a href={pokemon.pokemon_species.url}>{pokemon.pokemon_species.name}</a>
</div>
)
})
:
null
}
</div>
)
}
}
export default TilesContainer
I'm trying to do Step 15 of this ReactJS tutorial: React.js Introduction For People Who Know Just Enough jQuery To Get By
The author recommends the following:
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
return (
<div className="alert alert-warning">
<strong>Oops! Too Long:</strong>
</div>
);
} else {
return "";
}
},
render() {
...
{ this.overflowAlert() }
...
}
I tried doing the following (which looks alright to me):
// initialized "warnText" inside "getInitialState"
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
this.setState({ warnText: "Oops! Too Long:" });
} else {
this.setState({ warnText: "" });
}
},
render() {
...
{ this.overflowAlert() }
<div>{this.state.warnText}</div>
...
}
And I received the following error in the console in Chrome Dev Tools:
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
Here's a JSbin demo. Why won't my solution work and what does this error mean?
Your solution does not work because it doesn't make sense logically. The error you receive may be a bit vague, so let me break it down. The first line states:
Cannot update during an existing state transition (such as within render or another component's constructor).
Whenever a React Component's state is updated, the component is rerendered to the DOM. In this case, there's an error because you are attempting to call overflowAlert inside render, which calls setState. That means you are attempting to update state in render which will in then call render and overflowAlert and update state and call render again, etc. leading to an infinite loop. The error is telling you that you are trying to update state as a consequence of updating state in the first place, leading to a loop. This is why this is not allowed.
Instead, take another approach and remember what you're trying to accomplish. Are you attempting to give a warning to the user when they input text? If that's the case, set overflowAlert as an event handler of an input. That way, state will be updated when an input event happens, and the component will be rerendered.
Make sure you are using proper expression. For example, using:
<View onPress={this.props.navigation.navigate('Page1')} />
is different with
<View onPress={ () => this.props.navigation.navigate('Page1')} />
or
<View onPress={ () => {
this.props.navigation.navigate('Page1')
}} />
The two last above are function expression, the first one is not. Make sure you are passing function object to function expression () => {}
Instead of doing any task related to component in render method do it after the update of component
In this case moving from Splash screen to another screen is done only after the componentDidMount method call.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
Image,
} from 'react-native';
let timeoutid;
export default class Splash extends Component {
static navigationOptions = {
navbarHidden: true,
tabBarHidden: true,
};
constructor(props) {
super(props)
this.state = { navigatenow: false };
}
componentDidMount() {
timeoutid=setTimeout(() => {
this.setState({ navigatenow: true });
}, 5000);
}
componentWillUnmount(){
clearTimeout(timeoutid);
}
componentDidUpdate(){
const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}
}
render() {
//instead of writing this code in render write this code in
componenetDidUdpate method
/* const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}*/
return (
<Image style={{
flex: 1, width: null,
height: null,
resizeMode: 'cover'
}} source={require('./login.png')}>
</Image>
);
}
}
Call the component props at each time as new render activity. Warning occurred while overflow the single render.
instead of
<Item onPress = { props.navigation.toggleDrawer() } />
try like
<Item onPress = {() => props.navigation.toggleDrawer() } />
You can also define the function overflowAlert: function() as a variable like so and it will not be called immediately in render
overflowAlert = ()=>{//.....//}