I'm fetching some data from the firebase realtime database. I have created a state in the constructor and initialised as an empty array. Later in the componentDidUpdate method, I have updated the state with setState method. The issue is the render method called twice in the component and data is getting multiplied each time.
this.state = {
values: [],
}
componentDidMount = () => {
firebase.database().ref('Table').once('value', (data) => {
var input = data.val();
this.setState({ values: input })
})
}
And the render method:
var val = []; //global variable declared before class declaration
render() {
{
this.state.values.map(item => {
val.push(
<List>
<ListItem>
<Text>{item["value"]}</Text>
</ListItem>
</List>
)
})
}
return(
<View>
{val}
</View>
)
}
And the list item is keep getting multiplied each time when the component renders. I have checked the doc but couldn't get a proper solution.
https://reactjs.org/docs/react-component.html#componentdidmount
Where is val defined?
Okay. That I have defined a global var. Declared it as an array before the class declaration
That's where your duplication comes from.
Better do it this way:
render() {
const val = this.state.values.map((item, index) => (
<List key={index}>
<ListItem>
<Text>{item.value}</Text>
</ListItem>
</List>
));
return <View>{val}</View>;
}
I didn't understand well the val variable but this code should work for you:
mapValues = list => list.map((item, index) => (
<List key={index}>
<ListItem>
<Text>{item.value}</Text>
</ListItem>
</List>
));
render() {
return (
<View>
{this.mapValues(this.state.values)}
</View>
);
}
Related
I want to set my state inside JSX expression and show my component if the condition is true. How can i achieve that? i tried this this first :
{(currenMonth !== item.orderDate)
&& (setCurrentMonth(item.orderDate) && <Item name={getMonthFromString(item.orderDate)} active />)
}
In a second solution i've created this function :
const ProductsList = () => {
const [currenMonth, setCurrenMonth] = useState('');
const renderItem = (month) => {
if (currenMonth !== month) {
setCurrenMonth(month);
return <Item name={getMonthFromString(month)} active />;
}
return null;
};
return(
<View style={styles.container}>
<FlatList
data={products}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<View>
{ renderItem(item.orderDate) }
</View>
);
}}
/>
</View>
);
}
But i'm getting an Error [Unhandled promise rejection: Invariant Violation: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.]
There are two ways: practically the way you're doing it inside JSX and then a separate rendering function. I'll recommend the latter. But mentioned, render is not part of setting state. So you actually have two separate problems.
...
const renderMonthItem = () => (
(<yourConditionals> ? <Item ... /> : null;
)
...
return (
<View> ...
{ renderMonthItem() }
... </View>
);
I'm building a react native app where a post has comments. I only want to
show the comments when the user clicks on load comments.... The problem
is how do I handle the state for each post (there are multiple posts). I tried
this but it's not working (renderPost is a loop):
const renderPost = ({ item, index}) => {
let fetchComments = false;
return (
<View style={[t.mB6]}>
<View style={[t.roundedLg, t.overflowHidden, t.shadow, t.bgWhite, t.hAuto]}>
<TouchableOpacity
key={item.id}
onPress={() => {
fetchComments = true;
}}>
<Text style={[t.fontBold, t.textBlack, t.mT2, t.mL4, t.w1_2]}>
load comments...
</Text>
</TouchableOpacity>
</View>
{ fetchComments ? <Comments postId={item.id}/> : null }
</View>
)
}
In the code above I set let fetchComments to true when the user clicks on load comments....
renderPost is a functional component that doesn't have its own render and its own state, you may resolve this passing a function that changes state through renderPost props in its Father React.Component.
Example:
//imports
class FatherComponentWithState extends React.component{
state={
fetchComments:false,
//(...OTHERSTUFFS)
}
setFetchComments = () =>{
this.setState({fetchComments:true})
}
render(){
return(
//(...FatherComponentStuffs)
{new renderPost({
setFetchComments: this.setFetchComments,
fetchComments:this.state.fetchComments,
//(...renderPostOtherStuffs like item, index)
})}
//(...FatherComponentStuffs)
)}}
The renderPost function will receive it with something like this:
const renderPost = (props) =>{
let fetchComments = props.fetchComments;
let setFetchComments = props.setFetchComments;
let item = props.item
let index = props.index
//...renderPost return remains the same
}
P.S.: If you have multiple renderPosts, you can use fetchComments as an array of booleans and set the state to true passing an index as parameter of the setFetchComments function.
I ended up pulling off what I wanted. However, it's giving me an array of the state instead of rendering each one separately. This is probably very simple and I'm more than likely over-complicating it but hey, any help would be nice.
Here's what I currently am dealing with
And here's a better example: https://i.imgur.com/WLDkbOb.gif
And lastly here's probably the best overview: https://imgur.com/a/zintqTA
constructor(props) {
super(props);
this.state = {
data: [],
loading: false,
}
}
ws = new WebSocket(URL)
componentDidMount() {
this.ws.onopen = () => {
console.log('connected')
}
this.ws.onmessage = e => {
const tbox = JSON.parse(e.data);
if(tbox.data && tbox.data.length > 0){
this.setState({
data : this.state.data.concat(tbox.data[0]),
})
}
}
this.ws.onclose = () => {
console.log('disconnected')
this.setState({
ws: new WebSocket(URL),
})
}
}
render() {
let { data } = this.state;
const chatBox = data.map(item => {
return (
<List
key={item.id}
dataSource={this.state.data}
renderItem={item => (
<List.Item >
<List.Item.Meta
avatar={<Avatar size="large" icon="user" />}
title={<div>{item.user} {item.date}</div>}
description={item.message}
/>
</List.Item>
)}
>
</List>
)
})
return (
<div>
<div>
{chatBox}
</div>
I'm trying to loop through the state and render each message separately
I think you don't need to loop through this.state.data[] because you are already setting data source to antd <List> component. antd List component handles collection of objects for us.
This would be the code for rendring your this.state.data:
const chatBox = <List dataSource={this.state.data}
renderItem={item => (
<List.Item >
<List.Item.Meta
avatar={<Avatar size="large" icon="user" />}
title={<div>{item.user}
{item.date}</div>}
description={item.message}
/>
</List.Item>
)}
>
</List>;
you can have a look at these links :
https://stackblitz.com/run
https://ant.design/components/list/
I managed to fetch data and show to UI with this code:
export default class BoxGarage extends Component {
render() {
let garage = this.props.garage;
garage.name = garage.name.replace('strtoreplace', 'My Garage');
let cars = garage.cars.length ?
garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
)
}) : (
<View style={styles.boxEmpty}>
<Text style={styles.textEmpty}>(No Cars)</Text>
</View>
);
return (
<View style={styles.boxGarage}>
<Text>{ garage.name }</Text>
{ cars }
</View>
)
}
}
Then I tried to change with a function, but no cars shown. What is missing?
export default class BoxGarage extends Component {
render() {
let garage = this.props.garage;
garage.name = garage.name.replace('strtoreplace', 'My Garage');
cars = function(garage) {
if (garage.cars.length) {
garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
);
});
}
else {
return (
<View style={styles.boxEmpty}>
<Text style={styles.textEmpty}>(No Cars)</Text>
</View>
);
}
}
return (
<View style={styles.boxGarage}>
<Text>{ garage.name }</Text>
{ cars(this.props.garage) }
</View>
)
}
}
And I think I should refactor for best practice either using constructor or just move the function outside render, but I don't know what it is. Please advice.
The reason your second code doesn't work is that you're not returning anything from your function if garage.cars.length > 0.
if (garage.cars.length) {
// Added a return statement on the next line
return garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
);
});
}
That said, i think your first version of the code was much cleaner. If a piece of code got complicated enough that i was tempted to make an inline function to do calculations, i'd either pull that out to another class method, or to another component. In your case though, just doing a ternary or an if/else will be much better.
I have an array stored in my redux store, but when I call notifications.map((x, i) => {} nothing actually renders to the view... however if I console.log x to the console then they print....
How do I get my array contents to render to the view?
import React from 'react'
import { connect } from 'react-redux'
import {List, ListItem} from 'material-ui/List'
const mapStateToProps = state => {
return {
notifications: state.notificationsReducer.notifications,
errorMessage: state.notificationsReducer.errorMessage
}
}
const notifications = ({notifications, errorMessage}) => {
notifications.map((x, i) => {
console.log(x.title)
})
return (
<List>
{notifications.map((x, i) => {
<ListItem key={i} primaryText={x.title} />
})}
</List>
)
}
const Notifications = connect(mapStateToProps)(notifications)
export default Notifications
Remove the brackets of arrow function inside the map.
<List>
{notifications.map((x, i) =>
<ListItem key={i} primaryText={x.title} />
)}
</List>
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Function_body
I Arrow functions can have either a "concise body" or the usual "block
body".
In a concise body, only an expression is needed, and an implicit
return is attached. In a block body, you must use an explicit return
statement.
you have to return a value from the function to get the result you want
return (
<List>
{notifications.map((x, i) => {
return <ListItem key={i} primaryText={x.title} />
})}
</List>
)
or simply by not opening a curly brackets in the first place (implicit return)
return (
<List>
{notifications.map((x, i) =>
<ListItem key={i} primaryText={x.title} />
)}
</List>
)