I have a functional react component with a style defined as below:
const StyledInnerItem = withStyles((theme) => ({
bgColor: {
backgroundColor: (props) => {
console.log('styledInnerItem color: ', props.color);
return 'red';
}
}
}))(InnerItem);
Basically I am trying to pass the props.color as the background color, however it's not even returning red which is set temporarily as the bg Color.
When I log the props, it is returning the color from the first render, and it is adding in the html file. but the styles only get added when I click on my component and the colors get applied to the component.
It works fine when the background Color is not a function and I need it to work the same by reading the color from the props.
The issue might be because the withStyles HOC only updates the styles when the component re-renders, but the props passed to the component do not trigger a re-render. One way to solve this is to pass the color value to the component as a state and update the state when the props change, which will trigger a re-render and the styles will get updated.
Here's how you can implement this:
const StyledInnerItem = withStyles((theme) => ({
bgColor: {
backgroundColor: (props) => props.color
}
}))(InnerItem);
class InnerItemWrapper extends React.Component {
state = {
color: 'red'
};
componentDidUpdate(prevProps) {
if (this.props.color !== prevProps.color) {
this.setState({ color: this.props.color });
}
}
render() {
return <StyledInnerItem color={this.state.color} />;
}
}
In this way, when the props.color changes, the component re-renders and the styles get updated accordingly.
Related
How can I convert this hook-based code to class-based code? Does the code still work?
I'm using a react/ASP.net Core template. My project consists of several components that are siblings.
I'm trying to send a state from a component to another one.
import { useState } from "react";
//the change is reflected in the ImageEditor component
const ImageEditor = ({ yourState }) => (
<p>State in the ImageEditor = {yourState}</p>
);
//ImageTile changes the state through the setYourState method
const ImageTile = ({ yourState, setYourState }) => (
<button onClick={() => setYourState("World!")}>
Change State from the ImageTile
</button>
);
//App is the parent component and contains both image editor and tile
const App = () => {
//the state which holds the image ID is declared in the parent
const [imageId, setImageId] = useState("Hello");
return (
<div>
<ImageTile yourState={imageId} setYourState={setImageId} />
<ImageEditor yourState={imageId} />
</div>
);
};
export default App;
You can see the complete code on:
https://codesandbox.io/s/billowing-brook-9y9y5?file=/src/App.js:0-775
A parent passes it’s state to a child via props. The child is not allowed to change its parents state, if a child wants to change a parents state then the parent passes a callback to the child that the child can call to change the state. This is fundamental to reacts state management. A child does not need to know how a parent stores it’s state (class instance, hook instance or state library).
if your application uses a global state manager like redux, then global state is mapped to props and a store action can be called to update global state. In this case the child does not need to know who else is using the state because it’s global.
class Foo extends Component {
constructor (props) {
super(props);
this.state = { myState: 0 };
this.setMyState = this.setMyState.bind(this);
}
setMyState (value) {
this.setState({
myState: value
});
}
render () {
return (
<MyChildCompoent myStat={this.state.myState} setMyState={this.setMyState} />
);
}
}
you'll need to declare the state in the parent:
state = {
someKey: '',
};
And then in the parent, define some function to update it:
updateSomeKey = (newValue) => {
this.setState({ someKey: newValue });
}
And then pass both of these values as props to your sibling components:
render() {
return (
<div>
<Sib1 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
<Sib2 someKey={this.state.someKey} updateSomeKey={this.updateSomeKey} />
</div>
)
}
You shouldn't need to in order to update the 'shared' state. In the code above, the someKey state can be updated in either component by calling the updateSomeKey function that is also available as a prop.
If either component calls that function (this.props.updateSomeKey('hello!')) the updated state will propagate to both components.
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}]}
I have a table with Drag and Drop method and everything works fine except Drop. So when I want to Drop item into the table's cell there's an error which is caused by infinite loop.
i would be grateful If somebody could explain the reason of this error.
import React, {Component} from 'react';
import { DropTarget } from 'react-dnd';
function collect(connect, monitor) {
return {
connectDropTarget: connect.dropTarget,
isOver: monitor.isOver,
item: monitor.getItem,
}
}
class DropCells extends Component {
state = {
tdValue: ""
};
render() {
const { connectDropTarget, isOver, item} = this.props;
const backgroundColor = isOver ? 'lightgreen' : 'white';
const dropped = isOver? this.setState({tdValue: this.props.item}): this.state.tdValue;
return connectDropTarget(
<td className="target" style={{ background: backgroundColor }}>
{dropped}
</td>
);
}
}
export default DropTarget('item', {}, collect)(DropCells);
You have a call to this.setState({tdValue: this.props.item}) inside of your render function. Every time your component re-renders, it will call this.setState which will trigger another render.
This question already has answers here:
Reactjs - Setting State from props using setState in child component
(2 answers)
Closed 5 years ago.
So as I understand, a component will re-render when there has been a change in props and componentWillMount shall run before re-rendering. At the moment my constructor and componentWillMount run as expected, but then the question prop changes which I need to update the user score state, but this change in question prop doesn't trigger the constructor or componentWillMount. As I shouldn't update the state inside the render function (the only place so far that I have been able to get access to the updated question prop), how can I make react recognise this change in it's props and then update the state? Hope that's understandable.
Here is my container
class FullTimeScoreContainer extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
userHomeScore: 1,
userAwayScore: 1
}
}
componentWillMount() {
getFTSAnswerStatus(this.props.question).then(foundScores => {
if ( foundScores.userHomeScore ) {
this.setState({
userHomeScore: foundScores.userHomeScore,
userAwayScore: foundScores.userAwayScore
});
}
})
}
render() {
const { option, question, questionIndex, user, configs, renderAs, showNextQuestionAfterFTS, total} = this.props;
// check if question is active or not
let ComponentClass;
if ( question[0].active ) {
ComponentClass = FullTimeScoreActive;
} else {
ComponentClass = FullTimeScoreLocked;
}
const changeScoreState = (team, val) => {
switch (team) {
case "home":
this.setState( (prevState) => ({ userHomeScore: prevState.userHomeScore + val }) );
break;
case "away":
this.setState( (prevState) => ({ userAwayScore: prevState.userAwayScore + val }) );
break;
default:
throw new Error("unrecognised team to change score state")
}
}
const onClickCallback = () => {
const p = this.props;
const s = this.state;
p.showNextQuestionAfterFTS();
p.recordFullTimeScoreAnswer(s.userHomeScore, s.userAwayScore, p.question, p.questionIndex, p.user, p.configs)
}
return (
<ComponentClass
imgSrc={imgSrc}
user={user}
answerStatus={answerStatus}
question={question}
onClickCallback={onClickCallback}
questionIndex={questionIndex}
total={total}
configs={configs}
userScores={this.state}
changeScoreState={changeScoreState}
/>
)
}
}
const mapStateToProps = state => {
return {
configs: state.configs,
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ recordFullTimeScoreAnswer, showNextQuestionAfterFTS }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(FullTimeScoreContainer);
export { FullTimeScoreContainer }
componentWillMount will only run before the first render. It doesn't get run before every render. So even if your state and props update, componentWillMount will not get called again.
The constructor function is the same as well.
You might be looking for componentWillReceiveProps (see docs). This lifecycle event is called when a mounted component is about to receive new props. You can update your state in this lifecycle event.
Note that componentWillReceiveProps only works on mounted components. Therefore, it will not get called the first time your component receives its' initial props.
A side note: Per the docs, you also don't want to introduce any side-effects or subscriptions in componentWillMount. Do that in componentDidMount instead.
I would like add a comment, but I don't have enough reputation...
a component will re-render when there has been a change in props
As I understand, you can't change the props, so component re-render on state changes.
I have a stateful Key component that represents a Key in a Keyboard like so:
import React from 'react';
class Key extends React.Component {
constructor(props) {
super(props);
this.state = {
id: props.id,
customClass: props.customClass,
value: props.value,
onAction: props.onAction,
active: false
};
this.handleAction = this.handleAction.bind(this);
this.resetActiveState = this.resetActiveState.bind(this);
}
handleAction(e) {
e.preventDefault();
this.setState ({
active: true
});
this.state.onAction(this.state.value);
//remove key pressed effect after 150 ms by resetting active state
_.delay(() => this.resetActiveState() , 150);
}
resetActiveState() {
this.setState ({
active: false
});
}
render() {
//conditionalProps is empty when active is false.
let conditionalProps = {};
let className = `default-btn ${this.state.customClass}`;
let displayValue = this.state.value;
//enable css attribute selector
if (this.state.active){
conditionalProps['data-active'] = '';
}
return (
<button id={this.state.id} key={this.state.id} className={className}
data-value={this.state.value} {...conditionalProps} onTouchStart={this.handleAction}>
{displayValue}
</button>
);
}
}
Key.defaultProps = {
customClass: '',
onAction: (val) => {}
};
export default Key;
onTouchStart is used to detect a touch event.
onTouchStart handler changes active state to true.
Component re-renders with the appropriate css to give key clicked
effect.
After 150ms, active state is set to false using resetActiveState().
Component re-renders without the key clicked effect css.
conditionalProps attribute is used to conditionally add css styles (using css attribute selector) to achieve 'key pressed' look in the rendered component.
This works as expected but I was wondering if it would be possible to refactor the component so I can extract the logic to maybe a parent component which I can then extend using the Key component.
This would be a perfect use case for a Higher Order Component.
This will allow you to abstract much of the functionality and pass it down to stateless components as props.
The React official docs do a great job of explaining how to use HOCs.