Make text become white upon changing value in react? React-spring? - javascript

I have a div, with player score, deaths and assists:
<div className="liveMatchPlayerScore">
{data.kill_count}/{data.death_count}/{data.assists_count}
</div>
Every time the kill or death count changes, I'd like the text to turn a bold white for 3 seconds.
I was thinking of using react-spring for this, specifically useTransitions, but the documentation shows examples using an array of items. I going to put each of the scores in an array, but it seems counterproductive.
Previously i tried wrapping the scores in an "Spring" component from react-spring but that only animated the scores on their initial render - not when they update.
How can I make the kill_count and death_count become white for 3 seconds upon changing value?
Thank you
I used #PeterAmbruzs solution, but i seem to be getting strange numbers. For example in the images below, first the score was 0/0/0 and the first number increased by 1. Instead of becoming 1/0/0, it became 01/0/0. I'm also getting absurdly high numbers for some reason. Does anyone know why this might be happening?

I have also a solution. I think it is quite simple. First you create a component for the animated numbers. I wrapped it in react.memo to update it only when its property change. You can see it is bold, and red at start, but after 3sec it became normal and black. But you can change the style whatever you want. I added skip property to prevent animation for example for the first render.
const FadeNumber = React.memo(({ value, skip }) => {
const style = useSpring({
from: { color: "red", fontWeight: "bold" },
to: { color: "black", fontWeight: "normal" },
delay: 3000
});
return (
<animated.span style={{ margin: "10px", ...(skip ? {} : style) }}>
{value}
</animated.span>
);
});
Now there is a trick to reRender the animation at property change. Simly put the value to the key. When the key changes a new component will be created, so the animation will be played again. I added a unique prefix to prevent side effects.
<FadeNumber skip={kill === 0} key={"a" + kill} value={kill} />
And the whole example:
https://codesandbox.io/s/react-spring-change-fade-out-j8ebk

https://stackblitz.com/edit/react-5sztov?file=index.js
setTimeout() is still a viable construct - even when working in React.
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
assistsCount: 0,
color: 'black',
deathCount: 0,
fontWeight: 'normal',
killCount: 0,
setStyleToReset: false,
};
}
increaseKillCount () {
this.setState((prevState, props) => {
return {
killCount: prevState.killCount + 1,
color: 'white',
fontWeight: 'bold',
setStyleToReset: true,
};
});
}
render() {
if (this.state.setStyleToReset) {
this.resetStyle();
}
return (
<div>
<div style={{
backgroundColor:'green',
}}>
<span style={{color:this.state.color, fontWeight:this.state.fontWeight}}>{this.state.killCount}</span>/{this.state.deathCount}/{this.state.assistsCount}
</div>
<button onClick={() => this.increaseKillCount()}>Increase Kill Count</button>
</div>
);
}
resetStyle() {
setTimeout(() => {
this.setState({
color: 'black',
fontWeight: 'normal',
});
}, 3000);
}
}
render(<App />, document.getElementById('root'));

Related

React Native: Style Dependent on State is updating, but not rendering accordingly

Some Background:
Currently I'm trying to implement a TouchableOpacity component that represents a switch, which has a state of either 'on' or 'off'. The component works as follows:
On load, the component reads using AsyncStorage using the current date as the key (if an entry exists for the key, then the component has the state 'on' (true), if not 'off' (false)
The background color of the child View component is related to the state ('on': backgroundColor is green; 'off': backgroundColor is 'none')
If the component is pressed, the component is disabled while waiting for the AsyncStorage method to complete.
If the component is pressed while the state is 'off', a new entry is written, and on completion, the state is set to 'on'.
If the component is pressed while the state is 'on', the entry with the given key is deleted, and the state is set to 'off'.
Current Code:
class Switch extends Component {
constructor(props) {
super(props);
this.state = {
switchStatus: null,
awaitingIO: false
};
}
componentDidMount() {
switchStatusOfDate(new Date()).then((response) => {
this.setState({ switchStatus: response != null ? true : false });
}).catch((error) => {
// error
});
}
render() {
const {switchStatus, awaitingIO } = this.state;
if (switchStatus == null) {
return <View style={[styles.circleButton]}>
<Text>Loading</Text>
</View>
}
const style = {
backgroundColor: switchStatus ? 'green' : 'none',
...styles.circleButton
};
return (<TouchableOpacity disabled={awaitingIO} onPress={this._updateSwitchStatus.bind(this)} >
<View style={style}>
{/* Some children */}
</View>
</TouchableOpacity>);
}
async _updateSwitchStatus() {
this.setState({ awaitingIO: true });
if (this.state.switchStatus) {
await unsetSwitchStatusForDate(new Date());
} else {
await setSwitchStatusForDate(new Date());
}
this.setState({
switchStatus: !this.state.switchStatus,
awaitingIO: false,
});
}
}
const styles = StyleSheet.create({
circleButton: {
flex: 0,
alignItems: 'center',
justifyContent: 'center',
width: 150,
height: 150,
borderWidth: 2,
borderRadius: 75,
borderColor: 'green'
},
});
export default Switch;
Storage Methods:
function getDateKey(date) {
let year = date.getFullYear();
let month = date.getMonth();
let day = date.getDate();
return `#${year}${month}${day}`;
}
export async function switchStatusOfDate(date) {
try {
return await AsyncStorage.getItem(getDateKey(date));
} catch (e) {
// error
}
}
export async function setSwitchStatusForDate(date) {
try {
let value = `${date.getHours()}:${date.getMinutes()}`;
return await AsyncStorage.setItem(getDateKey(date), value);
} catch (e) {
// error
}
}
export async function unsetSwitchStatusForDate(date) {
try {
await AsyncStorage.removeItem(getDateKey(date));
} catch (e) {
// error
}
}
The Problem:
Currently, on load, the background color is set correctly (if the component is pressed to set the state to 'on', and an entry is written, upon reloading the application, the component background color is green). Also, when the state is loaded as 'off' (no entry exists), and I press the component, the background color changes from 'none' to green correctly. However, all further presses have no impact on the background color.
The state is being set correctly, and the AsyncStorage methods are also working. I've tried logging the switchStatus and style in the render() method, and the values returned are correct, but it doesn't have any effect on the actual render (it stays green no matter).
Does anyone know why this might be?
I feel like there's an obvious solution to this that I'm just missing, so any help would be appreciated! Thank you in advance.
Edit 1: I did alter some variable names so the code is self-contained to the question, but nothing else was changed (just in case there's any errors that are spotted caused by it).
Edit/Update 2: I've noticed that when using the element inspector with expo on my phone, the css property backgroundColor: 'none' is applied to the view, but it's background in the render is still green. This isn't due to any children components either, as I've removed them and it's still the case.
I've found that using,
backgroundColor: 'rgba(0, 0, 0, 0)'
or
backgroundColor: 'transparent'
where the last value in the first one is the opacity (in this case, completely transparent), works.
My problem was that I thought 'none' was a valid value for backgroundColor in css. This was not the case, whoops!

Why doesn't react update the style in the setState?

Why doesn't react update the style in the setState? The color of the text does not update to green with the setState function, it remains blue.
class Practice extends Component {
state = {
count: 0,
color: "blue"
}
style = {
color: this.state.color
}
handleIncrement = () => {
this.setState({ count: this.state.count});
this.setState({ color: "green"});
}
render() {
return(
<div>
<h1 style={this.style}>
The color in this state is {this.state.color}.
The number in count is {this.state.count}.
<button
onClick={this.handleIncrement}
>
Increment
</button>
</h1>
</div>
);
}
}
Information on how a component should render should flow from state alone - this will allow you to call setState to change how the component renders. Putting a .style property on the component instance itself won't work - put it into the state instead.
Rather than duplicating the color in different parts of the state, put it in just one place, in the style object in state.
Not 100% certain, but you also probably want
this.setState({ count: this.state.count});
to be
this.setState({ count: this.state.count + 1 });
class Practice extends React.Component {
state = {
count: 0,
style: {
color: 'blue',
}
}
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
this.setState({ style: {
...this.state.style,
color: "green"
}});
}
render() {
return(
<div>
<h1 style={this.state.style}>
The color in this state is {this.state.style.color}.
The number in count is {this.state.count}.
<button
onClick={this.handleIncrement}
>
Increment
</button>
</h1>
</div>
);
}
}
ReactDOM.render(<Practice />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
While other answers explain well why your code is not working in terms of React state, there is one thing I noticed that might be another point of confusion.
When you're assigning a value to your style property like this:
style = {
color: this.state.color
}
you might be thinking that you're assigning style.color a "reference" to the string that this.state.color points to. What actually happens is that you're assigning the value "blue", because the string is a primitive type in JS. So after you've done that, your style object is really just
{
color: "blue"
}
So even if you somehow change the value of this.state.color, this fundamentally wouldn't result in updating the value of this.style.color.
state in react app will only update using setState if and only if you have inclued the state in this.state if you are using constructor or state property without constructor.
Live Demo
You should create state as:
state = {
count: 0,
style: {
color: "blue"
}
};
and update the state as:
handleIncrement = () => {
this.setState((oldState) => {
return {
count: oldState.count + 1,
style: {
...oldState.style,
color: "green"
}
};
});
};

React Native set style as State

I want to use backgroundColor of style1 as a state, and change it in the function change().
How can I access style1?
My point is to call the function change from another function, making the button change its color to yellow, then change it's colour to blue again after sometime.
export default class App extends Component{
constructor (props){
super(props);
this.state = {
//style1.backgroundColour: blue //? Can't
}
this.change=this.change.bind(this)
}
change() {
this.setState({ style1.backgroundColour: yellow }) //?
}
render(){
return (
<View style={styles.style1} > </View>
);
}
}
const styles = StyleSheet.create({
style1:{
padding: 5,
height: 80,
width: 80,
borderRadius:160,
backgroundColor:'blue',
},
});
Update: This question and my answer was based on class components. But functional components and hooks are the current way of using React for a long time now.
First you should create a state for your backgroundColor:
const [backgroundColor, setBackgroundColor] = useState('blue');
then change it's value in your function
setBackgroundColor('yellow');
and finally, use it in style prop:
style={[styles.style1, {backgroundColor}}
Old answer, using class components
You can give an array to style prop. So you can put any other styles from another sources.
First you should declare a default value for your backgroundColor state:
this.state = {
backgroundColor: 'blue',
};
then set it's state in your function as normal string state:
this.setState({backgroundColor: 'yellow'});
and finally, use it in style prop:
style={[styles.style1, {backgroundColor: this.state.backgroundColor}]}

React Native: TextInput with state and AsyncStorage

When typing on the keyboard I was seeing some warnings about the input being ahead of the JS code..
Native TextInput(react native is awesome) is 4 events ahead of JS - try to make your JS faster.
So added the debounce and got this to "work":
...
import { debounce } from 'lodash'
...
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data,
indexRef: data.reduce((result, item, index) => {
result[item.title] = index
return result
}, {}),
ready: false,
}
this.updatePitch = this.updatePitch.bind(this)
this.saveLocally = debounce(this.saveLocally, 300).bind(this)
}
...
updatePitch(id, text) {
// Copy the data
let data = [...this.state.data]
const index = data.findIndex(obj => obj.id == id)
data[index].pitch = text
// Update the state
this.setState({ data }, this.saveLocally(data))
}
saveLocally(data) {
try {
AsyncStorage.setItem('data', JSON.stringify(data))
this.forceUpdate()
} catch (error) {
// Well..
}
}
render() {
...
BTW: I'm doing some "prop drilling" for now - but planning to do use the Context API (react 16.3)
The warning seems to have gone by adding debounce (1).. But I'm seeing some strange issues - particularly on the iPhone 8 plus simulator (not seeing the same on iPhone 6 simulator or Android device)
Issues observed:
TextInput don't expand - it just add scolling (expands on iPhone 6 and Android device)
Some layout issues in the FlatList - seems like it has problems finding correct height of list elements..
What is the best practice for fast JS code and saving to both state and AsyncStorage?
(1) One other way than using debounce could be to use getDerivedStateFromProps and add some sort of timer pushing the state to the parent component after some period of time.. But wasn't sure that this would make the JS code faster. So didn't try it.
UPDATE (again)
I open sourced the entire code since it is too hard to give all the needed information in a SO post when the code is so nested.
The entire code is here:
https://github.com/Norfeldt/LionFood_FrontEnd
(I know that my code might seem messy, but I'm still learning..)
I don't expect people to go in and fix my code with PR (even though it would be awesome) but just give me some code guidance on how to proper deal with state and AsyncStorage for TextInput.
I know I have some style issues - would love to fix them, but also comply with SO and keep it on topic.
Update II
I removed forceUpdate and replaced FadeImage with just vanilla react native Image.
but I'm still seeing some weird issues
Here is my code
import React from 'react'
import {
StyleSheet,
SafeAreaView,
FlatList,
StatusBar,
ImageBackground,
AsyncStorage,
Platform,
} from 'react-native'
import SplashScreen from 'react-native-splash-screen'
import LinearGradient from 'react-native-linear-gradient'
import { debounce } from 'lodash'
import Section from './Section'
import ButtonContact from './ButtonContact'
import { data } from '../data.json'
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data,
indexRef: data.reduce((result, item, index) => {
result[item.title] = index
return result
}, {}),
ready: false,
}
}
async componentDidMount() {
SplashScreen.hide()
try {
let BusinessPlan = await AsyncStorage.getItem('BusinessPlan')
if (BusinessPlan !== null) {
// We have data!!
let data = JSON.parse(BusinessPlan)
data = this.state.data.map(item => {
const index = data.findIndex(obj => obj.id == item.id)
const pitch = index >= 0 ? data[index].pitch : ''
return { ...item, pitch }
})
this.setState({ data, ready: true })
} else {
this.setState({ ready: true })
}
} catch (error) {
// Error retrieving data
}
}
updatePitch = (id, text) => {
// Copy the data
let data = [...this.state.data]
const index = data.findIndex(obj => obj.id == id)
data[index].pitch = text
// Update the state
this.setState({ data }, this.saveLocally(data))
}
saveLocally = data => {
try {
AsyncStorage.setItem('BusinessPlan', JSON.stringify(data))
} catch (error) {
// Well..
}
}
render() {
return (
<LinearGradient
style={{ flex: 1 }}
start={{ x: 0.0, y: 0.25 }}
end={{ x: 0.5, y: 1.0 }}
colors={['#000000', '#808080', '#000000']}
>
<StatusBar
barStyle={'light-content'}
backgroundColor={Platform.OS == 'iOS' ? 'transparent' : 'black'}
/>
<SafeAreaView>
<ImageBackground
source={require('../images/BackgroundImage.png')}
style={{ width: '100%', height: '100%' }}
resizeMode={'cover'}
>
<FlatList
data={this.state.data}
initialNumToRender="16"
keyExtractor={item => item.id}
renderItem={({ item }) => (
<Section
id={item.id}
title={item.title}
pitch={item.pitch}
updatePitch={debounce(this.updatePitch, 1000)}
questions={item.questions}
ready={this.state.ready}
/>
)}
ListFooterComponent={<ButtonContact />}
style={{
backgroundColor: 'transparent',
borderColor: '#000',
borderWidth: StyleSheet.hairlineWidth,
}}
/>
</ImageBackground>
</SafeAreaView>
</LinearGradient>
)
}
}
const styles = StyleSheet.create({
sectionHeader: {
fontSize: 24,
marginHorizontal: 5,
},
})
(I also updated my git repo)
Update III
It seems that the setup I have for state and AsyncStorage works fine with a debounce. The issues I was seeing was because I'm draining the CPU (next step to fix).
I tried your code:
"I'm seeing some strange issues - particularly on the iPhone 8 plus
simulator (not seeing the same on iPhone 6 simulator or Android
device)" ==> I confirmed this
The app takes about 100% CPU.
After a while trying I figured out:
"I'm seeing some strange issues - particularly on the iPhone 8 plus
simulator (not seeing the same on iPhone 6 simulator or Android
device)" ==> doesn't right, just wait a little TextInput will expand.
There are nothing wrong with state and AsyncStorage. I didn't get any warning.
The root issue is the animation in FadeImage :
The app render many Carousel, and each Carousel has AngleInvestor, and FadeImage. The problem is FadeImage
FadeImage run Animated with duration 1000 => CPU is overloaded
==> That why TextInput add scroll then take a long time to expand, and FlatList look like has problem, but not. They are just slowly updated.
Solution:
Try to comment FadeImage, you will see the problem gone away.
Don't start so many animations as the same time. Just start if it appears (Ex: the first card in Carousel )
UPDATE
I got your problem: typing fastly cause setState call so many times.
You use can debounce for this situation:
In App.js
render() {
console.log('render app.js')
...
<Section
id={item.id}
title={item.title}
pitch={item.pitch}
updatePitch={debounce(this.updatePitch, 1000)} // the key is here
questions={item.questions}
ready={this.state.ready}
/>
You can change the delay and watch the console log to see more. As I tried, delay about 500 can stop the warning.
P/s: You should try to remove forceUpdate

React Native different categories rendered to header component

I am developing a React Native quiz application. I already have developed the header component and it looks fantastic.
// import libraries for making a component
import React from 'react';
import { Text, View } from 'react-native';
// make a component
const Header = (props) => {
const { textStyle, viewStyle } = styles;
return (
<View style={viewStyle}>
<Text style={textStyle}>{props.headerText}</Text>;
</View>
);
};
const styles = {
viewStyle: {
backgroundColor: '#F8F8F8',
justifyContent: 'center',
alignItems: 'center',
height: 60,
paddingTop: 15,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
elevation: 2,
position: 'relative'
},
textStyle: {
fontSize: 20
}
};
// make a component available to other parts of the app
export default Header;
I am going to be doing an ajax request to an api that has a category and then one quiz question per category.
When I make a GET request to this endpoint I get back an array of objects and each object has a question category and one boolean question of true or false.
So in total I have 10 categories with a question for each category for a total of 10 questions.
So I need to figure out how to make an Ajax request or an http request from my mobile application to fetch this list of data and more critically, once I have the list of data, I need to figure out a way to get the category piece to render as the header.
This is the part I am stuck on.
I plan to have a component called QuestionList, the purpose of QuestionList will be to fetch the list of data or fetch the list of questions and once fetched, it will render several QuestionDetail components. So I will have a QuestionList and a QuestionDetail.
But again the api has a category for each question and I want to render that category content as the header for each question. So a different header each time the user goes to the next question.
So in the code above, rather than letting the header component decide what text should be displayed I refactored it slightly so that the app component will decide what text to show in there.
Did I just mess this up in terms of what I am trying to accomplish? Should I have let the header component decide what text should be displayed? And more importantly how do I get the header component or the App component to render the different categories? Should I be creating a separate CategoryList component and then a CategoryHeader component for each specific component that renders a different category from the api?
Based on your requirements you need a SectionList.
It already has a support for renderSectionHeader where you can integrate your Header component
An example to that would be
<SectionList
renderItem={({ item, index, section }) => <QuestionDetails {...item} />} // Render your Question Details Component here
renderSectionHeader={({ section: { title } }) => <Header {...title}/>} //... Render your Custom Header Component here
sections={[
{ title: 'Category1', data: ['question1', 'question2'] },
{ title: 'Category2', data: ['question1', 'question2'] },
{ title: 'Category3', data: ['question1', 'question2'] },
]}
keyExtractor={(item, index) => item + index} />
This is just a sample, you can modify to generate the array based on this that suits your requirements.

Categories

Resources