React Native: Component rerender but props has not changed - javascript

I'm encountering this strange issue that I can figure out why is happing.
This should not be happening since the prop passed down to the History component has not been updated.
./components/History.js
...
const History = ({ previousLevels }) => {
return (
<ScrollView style={styles.container}>
{previousLevels.reverse().map(({ date, stressValue, tirednessValue }) => {
return (
<CardKBT
key={date}
date={date}
stressValue={stressValue}
tirednessValue={tirednessValue}
/>
)
})}
</ScrollView>
)
}
...
export default History
As can be seen in this code (below), the prop to the History is only updated once the user press Save.
App.js
import React from 'react'
import { View, ScrollView, StyleSheet } from 'react-native'
import { AppLoading, Font } from 'expo'
import Store from 'react-native-simple-store'
import { debounce } from 'lodash'
import CurrentLevels from './components/CurrentLevels'
import History from './components/History'
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isLoadingComplete: false,
currentLevels: {
stressValue: 1,
tirednessValue: 1,
},
previousLevels: [],
}
this.debounceUpdateStressValue = debounce(this.onChangeStressValue, 50)
this.debounceUpdateTirednessValue = debounce(
this.onChangeTirednessValue,
50
)
}
async componentDidMount() {
const previousLevels = await Store.get('previousLevels')
if (previousLevels) {
this.setState({ previousLevels })
}
}
render() {
const { stressValue, tirednessValue } = this.state.currentLevels
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
...
/>
)
} else {
return (
<View style={{ flex: 1 }}>
<CurrentLevels
stressValue={stressValue}
onChangeStressValue={this.debounceUpdateStressValue}
tirednessValue={tirednessValue}
onChangeTirednessValue={this.debounceUpdateTirednessValue}
onSave={this.onSave}
/>
<History previousLevels={this.state.previousLevels} />
</View>
)
}
}
...
onChangeStressValue = stressValue => {
const { tirednessValue } = this.state.currentLevels
this.setState({ currentLevels: { stressValue, tirednessValue } })
}
onChangeTirednessValue = tirednessValue => {
const { stressValue } = this.state.currentLevels
this.setState({ currentLevels: { stressValue, tirednessValue } })
}
onSave = () => {
Store.push('previousLevels', {
date: `${new Date()}`,
...this.state.currentLevels,
}).then(() => {
Store.get('previousLevels').then(previousLevels => {
this.setState({
currentLevels: { stressValue: 1, tirednessValue: 1 },
previousLevels,
})
})
})
}
}

The component will re-render when one of the props or state changes, try using PureComponent or implement shouldComponentUpdate() and handle decide when to re-render.
Keep in mind, PureComponent does shallow object comparison, which means, if your props have nested object structure. It won't work as expected. So your component will re-render if the nested property changes.
In that case, you can have a normal Component and implement the shouldComponentUpdate() where you can tell React to re-render based on comparing the nested properties changes.

Related

In React Context, how can I use state variables in state functions?

I have a React Context which looks like this:
import React, { Component } from 'react'
const AlertsContext = React.createContext({
categoryList: [],
setCategoryList: () => {}
})
export class AlertsProvider extends Component {
state = {
categoryList: [],
setCategoryList: categoryString => (
this.categoryList.includes(categoryString)
? this.setState({ categoryList: this.categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: this.categoryList.concat([categoryString]) })
)
}
render() {
const { children } = this.props
const {categoryList, setCategoryList } = this.state
return (
<AlertsContext.Provider value={{categoryList, setCategoryList}}>
{children}
</AlertsContext.Provider>
)
}
}
export const AlertsConsumer = AlertsContext.Consumer
So, categoryList is an array of strings, each representing a category. setCategoryList should take a string; if that string is already in the array, it removes it, and if it's not in the array it adds it.
In one of my components the user can select categories from a list of checkboxes. When a checkbox is clicked, the AlertsContext setCategoryList should be called with the value of the clicked box:
import React, { Component } from 'react'
import { AlertsConsumer } from '../../../context/alerts-context'
class AlertFilters extends Component {
constructor(props) {
super(props)
this.state = {
categories: props.categories
}
}
render() {
const { categories } = this.state
return (
<AlertsConsumer>
{({ categoryList, setCategoryList }) => (
<>
{
categories.map(category => (
return (
<div key={category.id}>
<Checkbox id={category.id} value={category.value} onChange={e => setCategoryList(e.target.value)} checked={categoryList.includes(category.value)} />
<label htmlFor={category.id}>{category.value}</label>
</div>
)
))
}
</>
)}
</AlertsConsumer>
)
}
}
export default AlertFilters
This compiles ok, but when I run it and click a checkbox I get the following error:
alerts-context.jsx:77 Uncaught TypeError: Cannot read property 'includes' of undefined
This is in the line:
this.categoryList.includes(categoryString)
in the Context Provider, suggesting that "this.categoryList" is undefined at this point.
I tried changing it to
this.state.categoryList.includes(categoryString)
but it said I had to use state destructuring, so I changed to:
setCategoryList: (categoryString) => {
const { categoryList } = this.state
categoryList.includes(categoryString)
? this.setState({ categoryList: categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: categoryList.concat([categoryString]) })
}
which highlighted the ternary operator and gave the following lint error:
Expected an assignment or function call and instead saw an expression.
What am I doing wrong?
Use if/else syntax to update the state.
setCategoryList: categoryString => {
const { categoryList } = this.state;
if (categoryList.includes(categoryString)) {
this.setState({
categoryList: categoryList.filter(value => value !== categoryString)
});
} else {
this.setState({ categoryList: categoryList.concat([categoryString]) });
}
};

There is an error on the return ( and I cannot understand why

import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
class MyStories extends React.Component {
addFavorite = (e) => {
this.setState({
bgcolor: "blue"
})
}
render () {
const { stories } = this.props;
const { storyBriefs } = this.props.stories.length > 0 ?
stories.map(t => (<div className="menu-inner-container"><p key={t.id}><Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<div className="addFavoriteCss"
style={{backgroundColor: this.state.bgColor}}
onClick={this.addFavorite}> Favorite</div>
</p></div>))
//refactor - create a button that will allow for us to mark which our favorites are
return (
{ this.props.storyBriefs }
);
}
}
const mapStateToProps = state => {
return {
stories: state.myStories
}
}
export default connect(mapStateToProps)(MyStories)
getting this error
./src/components/MyStories.js
Line 26: Parsing error: Unexpected token, expected ":"
return (
^
{ this.props.storyBriefs }
);
}
I converted a functional component to a class component so that I could manipulate the state in order to change the color of the favorite button -- (I cannot use hooks or redux for the button function) Can anyone tell me what I am doing wrong?
You need to complete the ternary operator by adding :
const storyBriefs = this.props.stories.length > 0 ?
stories.map(t => (<div className="menu-inner-container"><p key={t.id}><Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<div className="addFavoriteCss"
style={{backgroundColor: this.state.bgColor}}
onClick={this.addFavorite}> Favorite</div>
</p></div>))
: [] // you need something here after the ':', an empty array could be useful in this case
return storyBriefs
or you could shorten it to
return stories.map(t => (<div className="menu-inner-container"><p key={t.id}><Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<div className="addFavoriteCss"
style={{backgroundColor: this.state.bgColor}}
onClick={this.addFavorite}> Favorite</div>
</p></div>))
As Jaromanda X said { this.props.storyBriefs } isn't valid. you need to provide the key value pair unless the variable doesn't have dot notation then you can define the object like that
This was the final code and it works,
import React from "react"
import { connect } from "react-redux"
import { Link } from "react-router-dom"
class MyStories extends React.Component {
constructor(props) {
super(props);
this.state = {
button: false
};
this.addFavorite = this.addFavorite.bind(this);
}
addFavorite = id => {
this.setState({
button: id
});
};
render() {
return this.props.stories.map(t => (
<div className="menu-inner-container">
<p key={t.id}>
<Link to={`/stories/${t.id}`}>{t.attributes.title}</Link>
<button
key={t.id}
className={this.state.button===t.id ? "buttonTrue" : "buttonFalse"}
onClick={() => this.addFavorite(t.id)}
>
Favorites
</button>
</p>
</div>
));
}
}
//refactor - create a button that will allow for us to mark which our favorites are
const mapStateToProps = state => {
return {
stories: state.myStories
};
};
export default connect(mapStateToProps)(MyStories);

React-JS How do I make a preloader icon appear before the image gallery is loaded?

I have a lightbox gallery coming through dependency react-lightbox-component.
Everything is working fine but before the gallery pop up on the screen I would like to show a preloader icon coming from the font-awesome:
<i className="fa fa-spinner"></i>
The font-awesome is already installed in my application and working fine. How do I imnplement the preloader with the lightbox gallery?
React-lightbox-Component documentation:
https://www.npmjs.com/package/react-lightbox-component
My component:
import React, { Component } from 'react'
import Lightbox from 'react-lightbox-component';
class PortfolioPage extends Component {
constructor(props) {
super(props);
this.state = {
resultPhotos: []
}
}
componentDidMount() {
this.setState({
resultPhotos: this.props.data.photos
})
}
render() {
const { resultPhotos } = this.state;
const renderImages = resultPhotos && resultPhotos.map((photo, index) => {
return (
{
src: `../images/${photo}`,
key: index
}
)
})
return (
<div>
<Lightbox images={renderImages} />
</div>
)
}
}
export default PortfolioPage
Try this way:
I changed the componentDidMount in order to set the photos already parsed.
I also changed the return section with two render possibilities.
Hope it helps.
import React, { Component } from 'react'
import Lightbox from 'react-lightbox-component';
class PortfolioPage extends Component {
constructor(props) {
super(props);
this.state = {
resultPhotos: []
}
}
componentDidMount() {
const parsePhotos = arr =>
arr.map((photo, index) => ({
src: `../images/${photo}`,
key: index
}));
const { data } = this.props;
if (data && data.photos) {
this.setState({
resultPhotos: [...parsePhotos(data.photos)]
})
}
}
render() {
const { resultPhotos } = this.state;
return (
<div>
{ !!resultPhotos.length
? (<Lightbox images={resultPhotos} />)
: (<i className="fa fa-spinner"></i>)
}
</div>
)
}
}
export default PortfolioPage
UPDATE
After a chat with #claudiobitar we found that it was a problem with the dependency react-lightbox-component.
It is not a problem of the PortfolioPage.jsx, but the Lightbox component.
If it is a dependency issue there is no much to do, sorry, just try another one.
If a dependency has less than 1000 downloads per week is a bad sign.
componentDidMount() {
this.setState({
resultPhotos: this.props.data.photos,
isReady = false,
})
}
render() {
const { resultPhotos, isReady } = this.state;
const renderImages = resultPhotos && resultPhotos.map((photo, index) => {
return (
{
src: `../images/${photo}`,
key: index
}
)
})
if (!isReady) return (<i className="fa fa-spinner"></i>);
return (
<div>
<Lightbox images={renderImages} />
</div>
)
}
Here you almost have everything you need, just find the right place where to put this.setState({isReady: true}).

How to implement search logic in javascript/react native/redux

I want to implement search logic that will be in some method, and then I would pass it to the TextInput props 'onChangeText'. I guess that I should iterate through array 'popularMovies' and find if my input value match the specific title. The problem is that I am not sure how that should look.
Thank you in advance!
import React, { Component } from 'react';
import { FlatList, View, StatusBar, TextInput } from 'react-native';
import { bindActionCreators } from 'redux';
import type { Dispatch as ReduxDispatch } from 'redux';
import { connect } from 'react-redux';
import { fetchPopularMovies } from '../../actions/PopularMovieActions';
import addToFavourite from '../../actions/FavouriteMovieActions';
import MovieCard from '../../components/movieCard/MovieCard';
type Props = {
fetchPopularMovies: Function,
popularMovies: Object,
navigation: Object,
}
class ListOfPopularContainer extends Component<Props> {
state = {
refreshing: false,
text: '',
}
componentDidMount() {
this.props.fetchPopularMovies();
}
search(text) {
this.setState({ text });
// SEARCH LOGIC SHOULD GO HERE
}
onRefresh() {
this.setState({ refreshing: true });
this.props.fetchPopularMovies();
this.setState({ refreshing: false });
}
render() {
const { popularMovies, navigation } = this.props;
return (
<View>
<TextInput
placeholder="Search movie"
onChangeText={ (text) => this.search(text) }
value={this.state.text}
/>
<StatusBar
translucent
backgroundColor="transparent"
barStyle="light-content"
/>
<FlatList
onRefresh={() => this.onRefresh()}
refreshing={this.state.refreshing}
data={popularMovies}
keyExtractor={item => item.title}
renderItem={({ item }) => (
<MovieCard
addToFavourite={() => this.props.addToFavourite(item)}
navigation={navigation}
card={item}
/>
)}
/>
</View>
);
}
}
const mapStateToProps = state => ({
popularMovies: state.popularMovies.popularMovies,
});
const mapDispatchToProps = (dispatch: ReduxDispatch): Function => (
bindActionCreators({ fetchPopularMovies, addToFavourite }, dispatch)
);
export default connect(mapStateToProps, mapDispatchToProps)(ListOfPopularContainer);
I'm unsure what you're asking, your setup is correct and this looks good. Do you want to know how to filter through your popular movies? This would be one way of implementing it with vanilla JS.
search(text) {
this.setState({ text });
// SEARCH LOGIC SHOULD GO HERE
let searchedMovies = this.state.popularMovies.filter(ele =>
ele.title.includes(text))
this.setState({searchedMovies})
}
you should check out lodash. two functions in particular:
Includes
https://lodash.com/docs/4.17.10#includes
this function checks if one string is included in another string.
import { includes } from 'lodash';
search(text) {
var searchResults = [];
var { popularMovies } = this.props;
for (var i = popularMovies.length -1; i >= 0; i--) {
includes(popularMovies[i], text) && searchResults.push(popularMovies[i])
}
return searchResults;
}
debounce
https://lodash.com/docs/4.17.10#debounce
this function throttles a function so as the user is typing it will regularly search at the phrase at reasonable intervals
debounce(search(text))

React Native multiple switches

On React-Native, I'm trying to create a screen with multiple switch components, with the possibility of selecting only one at once. When the component loads, only the first switch in on. if you click on it, it turns to off, but if you turn on another one, all the others turn to off.
I'm not sure I have the right approach here, as I'm confused about how to use the component state to do this.
In JS, I would do something like a function that turns all switch to off, but turn on the clicked one, but I don't understand how to this with state.
thanks in advance
import React from 'react'
import { ScrollView, Text, View, Switch } from 'react-native'
class switchScreen extends React.Component {
constructor (props) {
super(props)
this.state = {
trueSwitchIsOn: true,
falseSwitchIsOn: false
}
}
switch = (value) => {
this.setState({ falseSwitchIsOn: value, trueSwitchIsOn: !value })
}
render () {
return (
<View>
<Switch
onValueChange={this.switch}
value={this.state.trueSwitchIsOn}
/>
<Switch
onValueChange={this.switch}
value={this.state.falseSwitchIsOn}
/>
<Switch
onValueChange={this.switch}
value={this.state.falseSwitchIsOn}
/>
</View>
)
}
}
I believe a more optimal solution would minimize the amount of state, and possibility of inconsistent data. Using one state variable to keep track of which switch is active (if any) can solve your problem pretty easily.
import React from 'react'
import { ScrollView, Text, View, Switch } from 'react-native'
class switchScreen extends React.Component {
constructor (props) {
super(props)
this.state = {
activeSwitch: null,
}
}
// A simple toggle method that takes a switch number
// And toggles it between on / off
toggleSwitch = (switchNumber) => {
this.setState({
activeSwitch: switchNumber === this.state.activeSwitch ? null : switchNumber
})
};
//
switchOne = (value) => { this.toggleSwitch(1) };
switchTwo = (value) => { this.toggleSwitch(2) };
switchThree = (value) => { this.toggleSwitch(3) };
render () {
return (
<View>
<Switch
onValueChange={this.switchOne}
value={this.state.activeSwitch === 1}
/>
<Switch
onValueChange={this.switchTwo}
value={this.state.activeSwitch === 2}
/>
<Switch
onValueChange={this.switchThree}
value={this.state.activeSwitch === 3}
/>
</View>
)
}
}
import React from 'react'
import { ScrollView, Text, View, Switch } from 'react-native'
class switchScreen extends React.Component {
constructor (props) {
super(props)
this.state = {
switchone:false,
switchtwo:false,
switchthree:false,
}
}
switchOne = (value) => {
this.setState({ switchone: !value,
switchtwo:false,switchthree:false,
})
}
switchTwo = (value) => {
this.setState({ switchtwo: !value,
switchone:false,switchthree:false,
})
}
switchThree = (value) => {
this.setState({ switchtree: !value,
switchone:false,switchtwo:false,
})
}
render () {
return (
<View>
<Switch
onValueChange={this.switchOne}
value={this.state.switchone}
/>
<Switch
onValueChange={this.switchTwo}
value={this.state.switchtwo}
/>
<Switch
onValueChange={this.switchThree}
value={this.state.switchthree}
/>
</View>
)
}
}
You can try something like below, you can keep array of switch states, in the example its an associative key, but you can change according to your need, with multi level switch states. (pardon the code formatting but it give you the idea)
constructor(props) {
super(props);
this.state = {
switchStates: {
companyName: true
}
}
}
toggle(item, index) {
const switchStates = this.state.switchStates;
switchStates[index] = !switchStates[index];
console.log(switchStates);
this.setState({ switchStates });}
render switch
<Switch
onValueChange={isSwitchOn =>
this.toggle({ isSwitchOn }, "companyName")
}
value={this.state.switchStates.companyName}
/>

Categories

Resources