React Native Dynamic Styling - reactjs

I am creating s crypto app where I want to change background color to red when the price went down and green when Price went up and then after 2 seconds change the background back to normal using setTimeout.
I tried two different methods to at-least change the backgroundColor but on both the occasion i got the following error
You attempted to set the key backgroundColor with the value #ffe5e5
on an object that is meant to be immutable and has been frozen.
I asked the separate question for the same but for some reason, the response I received was not convincing.
Afterwords, i tried a different approach (the one which does not allow the use of StyleSheet) but I still got the same error.
I am putting my new code here (you can refer to my previous code from the question)
First I declared an object in a global scope like this
var upperRow = {
display: "flex",
flexDirection: "row",
marginBottom: 5,
backgroundColor: "white"
}
class CoinCard extends Component {
then I tried to change background color like this
componentWillReceiveProps(nextProps) {
if (this.props.coinPrice != nextProps.coinPrice ) {
if (this.props.coinPrice > nextProps.coinPrice) {
upperRow["backgroundColor"] = "#ffe5e5";
}
}
followed by assigning the styles like this
return (
<View style={container}>
<View style={upperRow}>
[Question:] How can I change the styling dynamically?

You'll need to keep in state whether or not your coin has decreased recently.
Imagine a state of this shape:
state = {
hasIncreased: boolean,
hasDecreased: boolean,
}
Now you can change this state in the componentWillReceiveProps (this is now deprecated though, so if you are on 16.3 or higher, maybe look in phasing its use out) like so:
componentWillReceiveProps(nextProps) {
if (nextProps.coinPrice < this.props.coinPrice)
this.setState({hasDecreased: true, hasIncreased: false})
if (nextProps.coinPrice > this.props.coinPrice)
this.setState({hasIncreased: true, hasDecreased: false})
}
Now that you have this state, you can add some styles conditionally:
render() {
const {hasIncreased, hasDecreased} = this.state
return (
<View style={[ //styles can be an array!
upperRow,
hasIncreased && {backgroundColor: 'green'},
hasDecreased && {backgroundColor: 'red'},
]}/>
)
}
Now all that's left is to reset state after 2 seconds. To me, it seems best to use the componentDidUpdate-lifecycle for that
componentDidUpdate(prevProps) {
// only queue the reset if it's actually necessary, not on random updates
if (prevProps.coinPrice !== this.props.coinPrice) {
clearTimeout(this.timeout)
// we need to be able to reset the timeout
this.timeout = setTimeout(
() => this.setState({hasDecreased: false, hasIncreased: false}),
2000,
)
}
}
You should try avoiding just changing the styles like that in a mutable manner. The whole point is that your state should reflect what you display.

Pass modalHeight dynamically from parent component as props
modalView is default style
<View style={[styles.modalView, { height: `${modalHeight}` }]}>

Related

Using Hooks API: does React respect setState order?

I have fairly nonexistent knowledge in react but I'm learning as I go. I learned the basics back in school, with class components (classic React), but now I'm delving into the Hooks API (mainly because I find it easier to learn and manage, although there seems to be more tricks involved regarding async behavior). So my question might seem silly.
I found this thread regarding setState behavior on the same topic, but this is regarding class components.
In my current application, I'm trying to set three different states using an event handler. It seems that the last state is set immediately, whereas the other two states remain undefined for a bit before changing to a real value. I'm using React-Native components for mobile development, so you'll see snippets in the code such as <SafeAreaView>.
export default App = () => {
const [ destLong, setDestLong ] = useState();
const [ destLat, setDestLat ] = useState();
const [ startNav, setStartNav ] = useState(false);
const [ locations, setLocations ] = useState([
{
name: 'IKEA',
long: '-74.00653395444186',
lat: '40.68324646680103',
},
{
name: 'JFK Intl. Airport',
long: '-73.78131423688552',
lat: '40.66710279890186',
},
{
name: 'Microcenter',
long: '-74.00516039699959',
lat: '40.67195933297655',
}
]);
const startNavigation = (goinglong, goinglat) => {
setDestLong(goinglong);
setDestLat(goinglat);
setStartNav(true);
}
return (
<SafeAreaView style={styles.container}>
{ startNav ?
<MapView
destLong = {destLong}
destLat = {destLat}
/>
:
<View style={styles.buttonContainer}>
<ScrollView>
{
locations.map((location, i) => {
return(
<Card
style={styles.card}
key={i}
title={ location.name }
iconName="home"
iconType="Entypo"
description={ location.long + ", " + location.lat }
onPress={() => startNavigation(location.long, location.lat)}
/>
);
})
}
</ScrollView>
</View>
}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
buttonContainer: {
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center'
},
logo: {
width: '50%',
height: '50%',
resizeMode: 'contain'
},
card: {
marginBottom: 10,
}
});
This throws an error, because MapView is expecting destLong and destLat to render properly. When I console log inside my startNavigation function, it seems that it immediately updates the state for startNav to true onPress, but destLong and destLat remain undefined for a few cycles before being set.
I've tried a different approach like this:
useEffect(() => {
setStartNav(true);
}, [destLong]);
const startNavigation = (goinglong, goinglat) => {
setDestLong(goinglong);
setDestLat(goinglat);
}
But it just crashes the app (my guess is infinite loop).
I've also tried removing the startNav state altogether and rendering <MapView> on destLong like this
{ destLong ?
<MapView
destLong = {destLong}
destLat = {destLat}
/>
:
<View style={styles.buttonContainer}>
...
</View>
}
But that did not work either.
Which brings me to this question: does the Hooks API respect the order of setState, or is each other carried out asynchronously? From my understanding it's the latter. But then, how do you handle this behavior?
I'm adding my comment here as well since I am unable to add proper formatting to my comment above.
Setting a state via useState is actually asynchronous, or rather the state change is enqueued and it will then return its new value after a re-render. This means that there is no guarantee in what order the states will be set. They will fire in order, but they may not be set in the same order.
You can read more here: https://dev.to/shareef/react-usestate-hook-is-asynchronous-1hia, as well as here https://blog.logrocket.com/a-guide-to-usestate-in-react-ecb9952e406c/#reacthooksupdatestate
In your case I would use useState and useEffect like this:
useEffect(() => {
if(destLong && destLat && !startNav) {
setStartNav(true);
}
}, [destLong, destLat, startNav]);
const startNavigation = (goinglong, goinglat) => {
setDestLong(goinglong);
setDestLat(goinglat);
}
With that said, I think you could further simplify your code by omitting the startNav state altogether and update your conditional render:
{ (destLat && destLong) ?
<MapView
destLong = {destLong}
destLat = {destLat}
/>
:
<View style={styles.buttonContainer}>
...
</View>
}
The above should have the same effect since you have two states that are undefined to begin with, and when they are both defined you want to render something and use their values.
And if you want to display the options again you can set the states to undefined again by doing setDestLat(undefined) and setDestLong(undefined)

pass material ui styles to component - react

I am using a functional component with React, I need to show SVG Icon based on state and I want to load the relevant icon
so the parent will show only call :
<icon classes:... , state..></icon>
1- how can I pass style and if it does not exist and use a default style in the child?
now I have smth like in the parent :
... createStyle
IconSuccess: {
fontSize: 20,
width: 20,
},
IconWarning: {
fontSize: 20,
width: 20,
},
but i want smth like :
icon:{
width:..
font ..
warning: { color}
success: { color}
}
then
<IconChild state={state} classes={{ icon: itemStyle.icon}} />
this is work only if I pass specific style like:
<IconChild state={state} classes={{ iconWarning: itemStyle.iconWarning}} />
then in the childCOmponent I am doing smth like:
const classes = useStyles(props);
if( props.state == 1){
return <className={`${classes.iconWarning}`} />
}
else{
return <className={`${classes.iconSuccess}`} />
}
so basically I am trying to understand how to create a really generic component that I can use and pass and that need a state to choose the specific icon and also from specific class
do I need HOC ? or different approach
As I understand, you want to:
Reuse some common properties like width and fontSize.
Custom render other properties like color.
Then this is my approach:
First, make new style for commonly used properties.
Secondly, create new styles for conditional use of each state.
Last, use something like classnames to combine all classes.
So the main idea here is: instead of using one class for each item, now using two classes for each one. That's it!
const useStyles = withStyles({
commonProperty: {
fontSize: '20px',
width: '20px',
},
successOnlyProperty: {
color: 'green'
},
warningOnlyProperty: {
color: 'orange'
},
});

Re-Rendering of ReactiveList Results

I am trying to build a Search-Engine using reactivesearch library. Does anybody know how i can trigger a re-rendering of the ReactiveList Results? (e.g. with a button onClick event?)
If you have a class component, please try below code
this.forceUpdate();
Unfortunately i can not provide my full code here. But i will try to explain what my problem is. Within my main App component i do have ReactiveBase, a DataSearch and ReactiveList as well as several buttons:
const App = () => (
<ReactiveBase
app="Test"
credentials="null"
url="http://localhost:9200"
analytics={false}
searchStateHeader
>
<DataSearch />
<ReactiveList
componentId="result"
dataField="_score"
renderItem={renderItem}
>
<div><Switch defaultChecked onChange={onSpeciesChange} style={{ marginRight: '5px', background: "brown" }} id="cellines"/> <label> Celline </label></div>
<div><Switch defaultChecked onChange={onSpeciesChange} style={{ marginRight: '5px', background: "blue" }} id="chemicals"/> <label> Chemicals </label></div>
So the buttons get rendered within my main App component and i do have a function onSpeciesChange, which basically updates a global object called entityswitchstatus with boolean values:
function onSpeciesChange(checked,event) {
if (event.target.id === "cellines") { entityswitchstatus.cellines=checked; }
else if (event.target.id === "chemicals") { entityswitchstatus.chemicals=checked; }
else if (event.target.id === "diseases") { entityswitchstatus.diseases=checked; }
else if (event.target.id === "genes") { entityswitchstatus.genes=checked; }
else if (event.target.id === "mutations") { entityswitchstatus.mutations=checked;}
else if (event.target.id === "species") { entityswitchstatus.species=checked; }
console.log(entityswitchstatus);
}
Within the renderItem function of the ReactiveList component i am processing the responses from Elasticsearch. And if there is a certain field and the global entityswitchstatus is true i do a highlighting of another field of the elasticsearch response. That all happens within renderItem function of ReactiveList.
function renderItem(res) {
if (res.ptc_species && entityswitchstatus.species) { var species_classname = createHighlighClassObject(res.ptc_species,"species"); } else { res.ptc_species = [] }
}
And basically by clicking the buttons i can change the global object entityswitchstatus of course. But this does not lead to a re-rendering of the ReactiveList component which is also expected. I can not pass any additional props to renderItem or at least i don't know how. So my idea was to simply call re-rendering of ReactiveList component by also clicking the button within the main App component.
Hope this is not too confusing.

Extending a React component and using the same method in the base component and the new component

When you extend a React component using composition, I'm not sure how to manage methods at multiple levels. Have a component that uses the method getTrProps to then apply styling for all instances of that component that we use. That gets applied for the extended component. But at the individual level it also needs getTrProps to add click handlers, and at the moment neither of them are run.
This comes from needing to apply the same styling code to every react-table we use but not wanting to include it every single time. To set the row height to always be 40px for example, you need to use getTrProps so extended-react-table calls this.normal Rows as shown below. However when you use this extended-react-table you need to set a row click event which you do by using getTrProps={this.rows}. It's the same method property which is what is causing an issue.
Extended-react-table (the extended version of react-table) itself has:
getTrProps={this.setRowDefaultsForAllReactTables} and then:
setRowDefaultsForAllReactTables= (state, rowInfo, column) => {
return { style: { height:40, display:'flex', alignItems: 'center'} }
}
with this code only being written once.
While the usage of extended-react-table has:
getTrProps={this.addRowEvents} and then:
addRowEvents= (state, rowInfo) => {
return { onClick: e => { this.props.openProduct(rowInfo.original); }
}
With this code being written for every instance of extended-react-table (and is always different as the onClick events required are always different).
Clearly they interfere with each other. What means should I be using to ensure that the events run at both levels? Or do I need to combine the props rather than have it run at both levels? So this.setRowDefaultsForAllReactTables combined somehow with this.props.getTrProps?
This is a standard case of having lots of boiler plate code over maybe 200 instances of the same component and not wanting to have to write it every time so you create a new component with all the common code in it so that you get all the default settings then in each instance you add anything that is specific to just that instance. So common row styling is in one place but row click events are in many places. The problem is there are two sets of thing to run but only one method. Here is the entire code for extended-react-table:
import * as React from 'react'
import ReactTable, {TableProps} from "react-table"
import './../components/ext-react-table.css'
import { Paper } from '#material-ui/core';
import Pagination from './pagination';
export interface IExtendedReactTableProps{
}
export interface IExtendedReactTableState {
}
export class ExtendedReactTable extends React.Component<IExtendedReactTableProps &
Partial<TableProps>, IExtendedReactTableState> {
constructor(props: IExtendedReactTableProps & Partial<TableProps>) {
super(props);
}
setDefaultSettingsForRows = (state, rowInfo, column) => {
return {
style: { height:40, display:'flex', alignItems: 'center'},
}
}
setDefaultSettingsForHeaderRows = (state, rowInfo, column) => {
return {
style: { height: 40, display:'flex', alignItems: 'center',
outline:'none'
}
}
render() {
return(
<Paper>
<ReactTable {...this.props} style={{cursor:'pointer', fontSize: 13,
background:'#fff', borderLeft: 0, borderRight: 0}} PaginationComponent=
{Pagination}
getTrProps={this.setDefaultSettingsForRows} getTheadThProps=
{this.setDefaultSettingsForHeaderRows} />
</Paper>)
}
}
And sample instance of its usage:
<ExtendedReactTable data={products} columns={[ {Header: "Product", accessor: "ProductName"}]}
defaultSorted={[ { id: "ProductName", asc: true }]}
defaultPageSize={10} className={" -highlight"}
getTrProps={this.addRowEvents} />
Currently neither or this.addRowEvents or this.setDefaultSettingsForHeaderRows run.

How to render list of instances without losing state/recreating component?

Experimenting with making my own React router with some animations. Hit a brick wall.
I'm rendering a stack of screens.
The stack can be popped or pushed.
My problem is that when the stack changes the state is lost and the constructor is called again destroying the previous state (making the stack useless) .
How would I do this?
Create the screen (After this we push to the stack which is on the state)
/**
* Create a new React.Element as a screen and pass props.
*/
createNewScreen = (screenName: string, props: ?any = {}): any => {
// Props is not an object.
if (typeof props !== 'object') {
Logger.error(`Passed props to screen name ${screenName} wasn't an object or undefined. Will error next screens.`);
return;
}
let propsUnlocked = JSON.parse(JSON.stringify(props));
// Add unique screen it.
const uniqueScreenKey = this.generateRandomUniqueID();
propsUnlocked.key = uniqueScreenKey;
propsUnlocked.screenId = uniqueScreenKey;
propsUnlocked.navKing = this;
propsUnlocked.screenName = screenName;
// Find the original screen to copy from.
// This just copies the 'type'
// $FlowFixMe
return React.createElement(this.findScreenNameComponent(screenName).type, propsUnlocked);
}
Render the screens
render() {
return ( <View
{...this.props}
onLayout={(event) => this.onLayout(event)}
pointerEvents={this.state.isAnimating ? 'none' : undefined}
>
{ this.renderStackOfScreens() }
</View>);
};
renderStackOfScreens() {
// Render screens.
return this.state.stackOfScreens
.map((eachScreen, index) => {
// Render second last screen IF animating. Basically because we have a screen animating over the top.
if (index === this.state.stackOfScreens.length - 2 && this.state.isAnimating) {
return (
<Animated.View
key={eachScreen.props.screenId + '_parent'}
style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}>
{ eachScreen }
</Animated.View>
);
}
// Render last screen which is animated.
if (index === this.state.stackOfScreens.length - 1) {
return (
<Animated.View
key={eachScreen.props.screenId + '_parent'}
style={this.getOffset(this.state.animatedScreenOffset)}>
{ eachScreen }
</Animated.View>
);
}
})
// Remove the undefined values.
.filter((eachScreen) => !!eachScreen);
}
Can see the full example here
https://pastebin.com/BbazipKt
Screen types are passed in as unique children.
Once a component is unmounted, its state is gone forever. You might think "well I have a variable reference to the component so even though it's unmounted it still keeps its state, right?" Nope, React doesn't work that way. Unmounting a component is tantamount to destroying it. Even if you remount the "same" component again, as far as React is concerned it's a brand new component, with a brand new constructor call, mounting lifecycle, etc. So you need to abandon your approach of keeping the React components themselves in arrays as the history stack.
Frustrating, I know. Believe me, I've run into the same problem.
The solution is to pull out your View/Screen states from the components themselves and lift them into a parent. In essence, you're keeping the states in an array in the parent, and then passing them as props into the Views/Screens themselves. This might seem like a counter-intuitive, "non-Reactful" way of doing things, but it actually is in line with how React is intended to be used. State should generally be "lifted up" to the level of the closest common ancestor that all components will need access to it from. In this case, you need access to your state at a level above the Views/Screens themselves, so you need to lift it up.
Here's some pseudocode to illustrate.
Right now, your app seems to be structured sorta like this:
// App state
state: {
// stackOfScreens is React components.
// This won't work if you're trying to persist state!
stackOfScreens: [
<Screen />,
<Screen />,
<Screen />
]
}
// App render function
render() {
return <div>
{
this.state.stackOfScreens.map((ea, i) => {
return <View key={i} >{ea}</View>
}
}
</div>
}
Instead it should be like this:
// App state
state: {
// stackOfScreens is an array of JS objects.
// They hold your state in a place that is persistent,
// so you can modify it and render the resulting
// React components arbitrarily
stackOfScreens: [
{
name: "screen#1",
foo: "Some sort of 'screen' state",
bar: "More state,
baz: "etc."
},
{
name: "screen#2",
foo: "Some sort of 'screen' state",
bar: "More state,
baz: "etc."
},
{
name: "screen#3",
foo: "Some sort of 'screen' state",
bar: "More state,
baz: "etc."
},
]
}
// App render function
render() {
return <div>
{
this.state.stackOfScreens.map((ea, i) => {
return <View key={i} >
<Screen stateData={ea} callback={this.screenCallback} />
</View>
}
}
</div>
}
Notice the addition of a callback prop on the Screen components that you're rendering. Thats so that you can trigger changes to the rendered Screen "state" (which is actually tracked in the parent) from within the Screen.

Resources