Component mounts several times, but only renders once - reactjs

I've got a component:
class Swatch1 extends Component {
componentWillMount() {
console.log("MERCHANT");
console.log(this.props.merchant);
}
lightestColor(colors) {
// Color calculations that set highestColor
console.log(highestColor);
}
render() {
return (
<ColorExtractor
src={this.props.merchant.merchant.coverPhoto}
getColors={colors => this.lightestColor(colors)}
/>
);
}
}
// mapStateToProps and mapDispatchToProps here
// Redux connection
const Swatch = connect(mapStateToProps, mapDispatchToProps)(Swatch1);
Now this component is being called every time another component is rendered, and for my current use-case, it should be being rendered three times...
Other component:
class Card extends Component {
render() {
return (
<div className="card titlecard" style={{backgroundImage: `url(${this.props.merchant.merchant.coverPhoto})`}}>
<Swatch merchant={this.props.merchant.merchant} />
....
CardRow component in it's entirely, including how the '' objects are being added.
class CardRow extends Component {
list = []
componentWillMount() {
this.list.push(<Card merchant={this.props.merchant} />)
}
render() {
return (
<div className="App">
<ScrollMenu
data={this.list}
/>
</div>
);
}
}
Right now, this is creating three <CardRow /> objects.
So theoretically, every time CardRow is rendered, Card (and thus Swatch1) should be rendered, and this seems to be consistent with componentWillMount() in Swatch1, but NOT lightestColor().
Why is componentWillMount() correctly being called multiple times, but not lightestColor()?
Shouldn't they both be called each time the Swatch1 component renders (which should be 3 times?)

Related

How can I import element to another React component?

I've got 2 components and want to get Panel_Menu element in another child component to do some stuff with it.
class Panel extends Component {
constructor(props){
super(props);
this.menuRef = React.createRef();
}
componentDidMount() {
console.log (this.menuRef.current)
// works correctly
}
render() {
return(
<>
<Panel_Menu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current}/>
</Panel_Menu>
</>
)
}
}
class Menu extends Component {
constructor(props) {
super(props);
}
isSame = () => {
const isSlideClass = this.props.item;
console.log(isSlideClass)
// is null
// expected output: → <div class="panel-menu"></div>
}
render() {
return (
<Left_Menu >
<Panel_Menu_Items className="test" onClick={this.isSame} />
</Left_Menu>
);
}
}
How can I update data in done render() to reach my goal?
Or... how can I get element instantly in external Component (Menu in this case) to do some stuff with it?
Issue
The issue here is that React refs, when attached on the initial render, will be undefined during the initial render. This means that item={this.menuRef.current} will enclose the initial undefined ref value in the click handler of the child.
Solution
It's simple, you really just need to trigger a rerender to reenclose an updated React ref value. You can either add some state to the Panel component and update it in the componentDidMount lifecycle method, or just issue a forced update.
class Panel extends Component {
menuRef = createRef();
componentDidMount() {
console.log(this.menuRef.current);
this.forceUpdate(); // <-- trigger rerender manually
}
render() {
return (
<>
<PanelMenu className="panel-menu" ref={this.menuRef}>
<Menu item={this.menuRef.current} />
</PanelMenu>
</>
);
}
}
Demo

Migration from componentWillReceiveProps to getDerivedStateFromProps

I am learning reactjs and I wrote component with the method componentWillReceiveProps (cWRP) but I read that it is deprecated and it must replace with getDerivedStateFromProps (gDSFP) - https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
Please note that the following code has the sole purpose of illustrating my problem and questions. It is not a full code.
App.js file :
import React from 'react';
import './App.css';
import Display from './component.js'
class App extends React.Component {
state={resetCounter:false}
resetCounter= () => this.setState( {resetCounter: true} );
render() {
return (
<div className="App">
<header className="App-header">
<Display resetCounter={this.state.resetCounter}></Display>
<div>
<p></p><p></p>
<button onClick={this.resetCounter}>Reset</button>
</div>
</header>
</div>
);
}
componentDidUpdate () {
if (this.state.resetCounter!==false)
this.setState( {resetCounter: false} );
}
}
export default App;
component.js file
import React from 'react'
class Display extends React.Component {
constructor() {
super();
this.state = this.resetState();
this.state.generalCounter=0;
}
/* method to avoid code duplication in constructor and cWRP
could not be used with getDerivedStateFromProps */
resetState = () => ({resettableCounter: 0,});
componentWillReceiveProps(nextProps) {
if (nextProps.resetCounter===true)
this.setState(this.resetState())
}
render() {
return (
<>
<div>
<div>general counter : {this.state.generalCounter}</div>
<div>resettable counter : {this.state.resettableCounter}</div>
</div>
<div>
<button onClick={this.incCounters}>+</button>
<button onClick={this.decCounters}>-</button>
</div>
</>
)
}
incCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter+1,
generalCounter: this.state.generalCounter+1
}
)
decCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter-1,
generalCounter: this.state.generalCounter-1
}
)
}
export default Display
In the state of the component, there is a resettable part and a non resettable one. A method resetState is used to avoid code duplication in the constructor and in cWRP.
To replace cWRP by gDSFP, I wrote a class method because instance method could NOT be called in gDSFP (this is not usable)
...
constructor() {
super();
this.state = Display.resetState();
this.state.generalCounter=0;
}
static resetState () {
return ({resettableCounter: 0,});
}
static getDerivedStateFromProps(nextProps) {
if (nextProps.resetCounter === true) {
return Display.resetState();
} else {
return null;
}
}
...
With this solution, it is very easy to modify all my components but I am not sure that it is a good mean.
I wonder if I have a misconception and if I should rewrite my components to separate them into Fully controlled components and Fully uncontrolled components with a key ( https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions).
For example, in this case, do I have to write :
One Fully uncontrolled components for the resettable counter
One Fully controlled one for the non resettable counter
A parent component with the +/- buttons to render them.
I ask this question because in some cases, it will be much work, so I want to be sure before continuing.
You would want to keep the gdsfp version in your post if your component depends on some outside props, which you don't have controll over (such as JSON returned or 3rd party render props component, etc).
It looks like you have a full control over what's passed down to the Display. You can pass down an initial resettableCounter value down to Display.
The advantage is two-folds.
Your Display props shows what the Display does - Making it more descriptivie/readable.
It's easier to maintain, as you don't have to massage the data.
For your particular case, Fully uncontrolled component with a key seems to make more sense, as Display should accept the initial value to show, but is responsible for managing the reseetableCounter.
Unless it's absolutely unavoidable, don't create components which control their siblings (or parents). Instead, lift state to a common ancestor:
const Display = ({
generalCounter,
resettableCounter,
incrementCounters,
decrementCounters,
}) => (
<div>
<div>General Counter: {generalCounter}</div>
<div>Resettable Counter: {resettableCounter}</div>
<button onClick={incrementCounters}>Increment</button>
<button onClick={decrementCounters}>Decrement</button>
</div>
);
class DisplayContainer extends React.Component {
state = {
generalCounter: 0,
resettableCounter: 0,
};
incrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter + 1,
resettableCounter: prevState.resettableCounter + 1,
}));
decrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter - 1,
resettableCounter: prevState.resettableCounter - 1,
}));
resetResettableCounter = () => this.setState({
resettableCounter: 0,
});
render() {
return (
<React.Fragment>
<Display
{...this.state}
incrementCounters={this.incrementCounters}
decrementCounters={this.decrementCounters}
/>
<button onClick={this.resetResettableCounter}>
Reset Resettable Counter
</button>
</React.Fragment>
);
}
}
const App = () => (
<div>
<DisplayContainer />
</div>
);
An alternative approach would be something like Redux (which effectively lifts state out of React).

How do I implement an onClick method in one child component that updates the text in a sibling component, based on the state in App.js?

Every row in my SideMenuContainer corresponds to an object from schema.json, showing only the name property. The behavior I want is that when a row is clicked, the PaneViewContainer toggles to display the name and other properties of that respective object from the json.
In App.js, the data is passed to SideMenuContainer like so:
render() {
return (
<MainContainer>
<SideMenuContainer genInfoList={this.state.genInfoList}/>
<PaneViewContainer genInfoList={this.state.genInfoList}/>
</MainContainer>
);
}
In SideMenuContainer, every row is populated like this:
render() {
return (
<SideMenuPane>
<SideMenu>
<div>
<h2>GenInfo</h2>
{this.props.genInfoList.map(genInfoElement => {
return (
<SideMenuRow>
{genInfoElement.name}
</SideMenuRow>
);
})}
</div>
</SideMenu>
</SideMenuPane>
);
}
What I want to do is change the genInfoList information being displayed in the PaneViewContainer based on which row is clicked in its sibling, SideMenuContainer.
The entire genInfoList data is being passed to both sibling components from their parent App.js, so I want to change which portion of that data is loaded in the Pane based on the row clicked in the SideMenu.
I thought about using the Context API, but I'm having trouble figuring out how to implement it for this purpose. Any ideas?
If I understand correctly you have your information stored in the parent element of both components then you can just pass a function down as a prop and have all of your logic stored in the parent element.
changeInfoList = id => {
//change your info list based on id or index or whatever
this.setState({
//your new list
})
}
render() {
return (
<MainContainer>
<SideMenuContainer changeInfoList={this.changeInfoList} genInfoList={this.state.genInfoList}/>
<PaneViewContainer genInfoList={this.state.genInfoList}/>
</MainContainer>
);
}
and then call changeInfoList from your component with props
render() {
return (
<SideMenuPane>
<SideMenu>
<div>
<h2>GenInfo</h2>
{this.props.genInfoList.map(genInfoElement => {
return (
<SideMenuRow>
{genInfoElement.name}
<button onClick={this.props.changeInfoList(genInfoElement.id)>CLick Me</button>
</SideMenuRow>
);
})}
</div>
</SideMenu>
</SideMenuPane>
);
}
this is commonplace in react as you should have smart components and dumb components. When you have components not in the same tree or spread far away then the context api is very useful. In your case I don't think its necessary.
Without external state management, you would have to pass down a callback (as props), so the children can update the parent's state.
As the components get far away from each other, this pattern can get annoying (passing down callbacks each time). That's where external state management can help.
Here's a simple (and untested) example using a callback:
class Counter extends React.Component {
constructor() {
super();
this.increment = this.increment.bind(this);
this.state = {count: 0};
}
increment() {
let count = thist.state.count;
this.setState({count: count + 1});
}
render() {
return <div>
<CounterButton increment={this.increment}/>
<CounterDisplay count={this.state.count}/>
</div>;
}
}
class CounterButton extends React.Component {
render() {
let increment = this.props.increment;
return <button onClick={increment}>Plus One</button>;
}
}
class CounterDisplay extends React.Component {
render() {
let count = this.props.count;
return <span>{count}</span>;
}
}

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

React Redux - pass data down to components via props or connect

I'm working on React Redux app and I have quite fundamental question about some kind of best practises.
I have MainComponent (kind of container) where I'm fetching data on componentDidMount:
class MainComponent extends React.Component {
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
render() {
return (
<div>
<ChildComponent1 />
<ChildComponent2 />
</div>
)
}
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
How to pass fetched data to ChildComponents? What is the best practise? Two possible solutions are (IMHO - maybe more?)
First solution:
class MainComponent extends React.Component {
...
render() {
return (
<div>
<ChildComponent1 dataOne={this.props.data.one} />
<ChildComponent2 dataTwo={this.props.data.two} />
</div>
)
}
...
Second solution - connect ChildComponents to store which is updated by fetchDataAction() in MainComponent:
class ChildComponent1 extends React.Component {
render() {
return (
<div>
{this.props.one}
</div>
)
}
}
function mapStateToProps(state){
return (
one: state.one
)
}
export default connect(mapStateToProps,null)(ChildComponent1)
Now I use first solution when ChildComponents do not fire actions which update store and second solution when they do. But I'm not sure if it is proper approach.
If you have multiple child components and you have to pass a part of fetched data to different child components ; I would suggest keep the parent component as single point of source.
You can try something like:-
class MainComponent extends React.Component {
constructor(){
super()
this.state = {
data : {}
}
}
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
componentWillReceiveProps(nextProps){
//once your data is fetched your nextProps would be updated
if(nextProps.data != this.props.data && nextProps.data.length>0){
//sets your state with you data--> render is called again
this.setState({data:nextProps.data})
}
render() {
//return null if not data
if(this.state.data.length === 0){
return null
}
return (
// it should have keys as one and two in api response
<div>
<ChildComponent1 data={data.one}/>
<ChildComponent2 data={data.two}/>
</div>
)
}
}
function mapStateToProps(state){
return (
data: state
)
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
I feel all logic stays at one place this way. Say if you plan to add to add few more child components in future,you only need to add a line of code above and few changes in API. However if you read in each component you have connect that component to store again which makes it more complex.
So if you dont have any other logic in your child components apart from getting data I would keeping this logic in the parent component.

Resources