I'm trying to better understand the role of keys in React components. I've read quite a bit but every example I've seen (like the one in the React docs or the great explanation on S.O.) assumes the data coming into the component is dynamic.
The examples all apply keys with array index values or using something like .map() to assign database IDs dynamically to each instance of the child component, and satisfy React's need for keys.
My example is on a static site with static content coming into the child component that gets called a couple of times. Best I figured, I could create a random number generator function getRandomInt and apply the key that way.
Unfortunately this results in the familiar React error:
Each child in an array or iterator should have a unique "key" prop.
Check the render method of CaseStudyOpinionSummary. It was passed a
child from DiagnosticCaseStudy.
Where am I going wrong?
Parent component (DiagnosticCaseStudy)
import React from 'react'
import CaseStudyOpinionSummary from '../../../components/CaseStudyOpinionSummary'
export default class DiagnosticCaseStudy extends React.Component {
getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
render() {
return (
<CaseStudyOpinionSummary
part="Part One"
partTitle="Diagnosis"
partSubtitle="Primary Care Encounter"
partSummary="Short brief"
key={ this.getRandomInt(0, 100000) }
/>
<CaseStudyOpinionSummary
part="Part Two"
partTitle="Medication and Management"
partSubtitle="Initial Gastroenterologist Encounter"
partSummary="Another short brief"
key={ this.getRandomInt(0, 100000) }
/>
)
}
Child component (CaseStudyOpinionSummary)
import React from 'react'
export default class CaseStudyOpinionSummary extends React.Component {
render() {
return (
<div>
<section className="lightest-gray-bg">
<section className="aga-cs-container-short">
<section className="aga-container">
<h2 className="aga-cs-orange-title">{[this.props.part, ": ", this.props.partTitle ]}</h2>
<h2 className="aga-cs-question-title">{ this.props.partSubtitle }</h2>
{ this.props.partSummary }
</section>
</section>
</section>
</div>
)
}
}
React only needs the key prop to distinguish between sibling components in an array. You don't need the key prop for regular sibling components.
class AppWithArray extends React.Component {
render() {
return (
<div>
{[
<div key="1"> test1 </div>,
<div key="2"> test2 </div>
]}
</div>
);
}
}
class AppWithoutArray extends React.Component {
render() {
return (
<div>
<div> test3 </div>
<div> test4 </div>
</div>
);
}
}
ReactDOM.render(
<div>
<AppWithArray />
<AppWithoutArray />
</div>,
document.getElementById("root")
);
<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="root"></div>
When a component gets a new key prop, the old one will be unmounted and thrown away and a new one will be created and mounted. You almost never use the key prop outside of arrays, but it can be a nice technique to keep in mind if you ever need to create an entirely new component.
class Timer extends React.Component {
timer = null;
state = { count: 0 };
componentDidMount() {
this.timer = setInterval(() => {
this.setState(prevState => ({ count: prevState.count + 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
return <div>{this.state.count}</div>;
}
}
class App extends React.Component {
state = { timerKey: 1 };
componentDidMount() {
setTimeout(() => {
this.setState({ timerKey: 2 });
}, 5000);
}
render() {
return <Timer key={this.state.timerKey} />;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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="root"></div>
Related
I'm loading some react components on demand (among with other information) depending on user input.
The components to render are kept in an array and the render method uses array.map to include the components.
The problem is, that if I trigger a forceUpdate() of the main app component, the mapped components won't update.
Code example: https://codesandbox.io/s/react-components-map-from-array-ekfb7
The dates are not updating because you are creating the instance of the component in your add function, and from then on you are referencing that instance without letting react manage the updates.
This is why storing component instances in state or in other variables is an anti-pattern.
Demonstration of the problem
Below I've created a working example still using forceUpdate just to prove what the issue is.
Notice instead of putting the component in state, I'm just pushing to the array to increase it's length. Then React can manage the updates correctly.
class TestComponent extends React.Component {
render() {
return <p>{Date.now()}</p>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.comps = [1];
}
add() {
this.comps.push(1);
this.forceUpdate();
}
render() {
return (
<div className="App">
<h1>Components map example</h1>
<p></p>
<h2>Static TestComponent (ok):</h2>
<TestComponent />
<h2>TestComponents mapped from an array (not ok):</h2>
{this.comps.map((comp, id) => {
return <div key={id}><TestComponent /></div>;
})}
<h2>All should update when the App component renders</h2>
<p>
<button onClick={() => this.add()}>Add TestComponent</button>
<button onClick={() => this.forceUpdate()}>forceUpdate App</button>
</p>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This is still a less than ideal solution. But it does show where the issue lies.
A better solution
If you need to know more about each component instance up front, you can make the array more complex.
I would also suggest using state to store the comps array, and removing forceUpdate completely.
class TestComponent extends React.Component {
render() {
return <p>{Date.now()} {this.props.a} {this.props.b}</p>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
comps: [{ a: 'a', b: 'b' }]
}
}
add = () => {
// add your custom props here
this.setState(prev => ({comps: [ ...prev.comps, { a: 'c', b: 'd' } ]}));
}
render() {
return (
<div className="App">
<h1>Components map example</h1>
<p></p>
<h2>Static TestComponent (ok):</h2>
<TestComponent />
<h2>TestComponents mapped from an array (not ok):</h2>
{this.state.comps.map((compProps, id) => {
return <div key={id}><TestComponent {...compProps} /></div>;
})}
<h2>All should update when the App component renders</h2>
<p>
<button onClick={() => this.add()}>Add TestComponent</button>
</p>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Now notice that each component in the map callback can have it's own unique set of props based on whatever logic you what. But the parts that should re-render will do so correctly.
In order to update in React, you have to put your data in the state and then setState.
setState() schedules an update to a component’s state object. When state changes, the component responds by re-rendering which means updating the screen with the new state.
import React from "react";
import "./styles.css";
class TestComponent extends React.Component {
render() {
return <p>{Date.now()}</p>;
}
}
export class App extends React.Component {
constructor(props) {
super(props);
this.state = {
comps: [<TestComponent />],
}
}
add = () => {
this.setState({ comps: this.state.comps.concat(<TestComponent />) })
}
render() {
return (
<div className="App">
<h1>Components map example</h1>
<p></p>
<h2>Static TestComponent (ok):</h2>
<TestComponent />
<h2>TestComponents mapped from an array (not ok):</h2>
{
this.state.comps.map((comp, id) => {
return <div key={id}>{comp}</div>;
})
}
<h2>All should update when the App component renders</h2>
<p>
<button onClick={this.add}>Add TestComponent</button>
</p>
</div>
);
}
}
I'm having trouble with lifting state up and converting components to functions. What's wrong with my code.
Instructions: 1: Inside the JS section, create a class component named App. Inside its render() method, have it return the Welcome component. In the ReactDOM.render() method, change Welcome to App.
2: Lift the state from the Welcome component to App, so that the state is initialized inside of App's constructor.
3: Convert the Welcome component to a function component that returns the same welcome message as before. You will need to pass the bootcampName property of state from App to the Welcome component. It's up to you whether or not to destructure it.
class App extends Component {
constructor(props) {
super(props);
this.state = {
bootcampName: "Nucamp"
};
}
render() {
return (
<div className="App">
<Welcome {this.state.bootcampName}>;
</div>
);
};
}
function Welcome(props) {
return (
<h1>Welcome to {this.props.bootcampName}!</h1>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
You have some errors there
You need to close the Welcome Component.
You need to name the prop
Destruct the props in because of this.state do not exist there.
Here the Code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
bootcampName: "Nucamp"
};
}
render() {
return (
<div className="App">
{ /**
* you need to close the Welcome Component
* you need to name the prop
*/}
<Welcome bootcampName={this.state.bootcampName}/>;
</div>
);
};
}
// Here destruct props to use it
function Welcome({bootcampName}) {
return (
<h1>Welcome to {bootcampName}!</h1>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
Name the prop:
<div className="App">
<Welcome bootcampName={this.state.bootcampName} />
</div>
When you use functional components, you no longer need to use this. Try doing this instead:
class App extends Component {
constructor(props) {
super(props);
this.state = {
bootcampName: "Nucamp"
};
}
render() {
return (
<div className="App">
<Welcome bootcampName={this.state.bootcampName}>;
</div>
);
}
}
function Welcome({bootcampName}) {
return (
<h1>Welcome to {bootcampName}!</h1>
);
}
I'm new to react and am trying to implement a simple for loop, as demonstrated in this other stackoverflow post. However, I cannot seem to make it work. I just want to run the component 5 (or any number of) times rather than map over an array or similar.
DEMO: https://stackblitz.com/edit/react-ekmvak
Take this example here:
index.js:
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
import Test from './test';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
render() {
return (
<div>
for (var i=0; i < 5; i++) {
<Test />
}
</div>
);
}
}
render(<App />, document.getElementById('root'));
test.js
import React from "react";
export default function Test() {
return (
<p>test</p>
);
}
Can anyone tell me where I'm going wrong? I've tried to copy the other stackoverflow post and tried test() also. I still get this error:
Error in index.js (18:27) Identifier expected.
Thanks for any help here.
You're trying to use plain Javascript in JSX. You have the right idea but your syntax is wrong. Instead, move your Javascript code (for loop) out to your render() method (above the return())
render() {
let items = []
for (let i = 0; i < 5; i++) {
items.push(<Test key={i} />)
}
return (
<div>
{items}
</div>
);
}
Few things to note here:
Components that are being iterated over, need a unique key property. In this case, we can use the current value of i
Elements can be rendered in JSX by wrapping them in curly braces, shown above. { items }
JSX will accept you any valid JavaScript expressions, Declarative vs Imperative Programming maybe this source can help you. You can create a declarative solution like those shown by the other colleagues (and the best solution), and also you can wrap your imperative solution into a function or method.
const Test = () => {
return (
<p>test</p>
);
}
class App extends React.Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
createElements = () => {
const elments = [];
for (var i=0; i < 5; i++) {
elments.push(<Test />)
}
return elements;
}
render() {
return (
<div>
{this.createElements()}
</div>
);
}
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<div id="react"></div>
<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>
You need a returned value inside the JSX to be able to display anything, here's how you can do that:
const Test = () => {
return (
<p>test</p>
);
}
class App extends React.Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
render() {
return (
<div> { Array.from(Array(5)).map(el => <Test />) } </div>
);
}
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
<div id="react"></div>
<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>
You can't use a for loop like this in your return. I would recommend you using a map for this and looping over an array. You can do this by simply creating an array and directly mapping over it :
[...Array(totalSteps)].map(el => el {
return (
<Test />
)
})
You will have to surround this whole block in {}. This will create an array of totalSteps items and return totalSteps <Test />. So if totalSteps is 5, you'll be rendering that component 5 times. This is how your final component should look like :
render() {
return (
<div>
{[...Array(totalSteps)].map(el => el {
return (
<Test />
)
})}
</div>
);
}
For Dynamic Implementation, you can just pass an object to the parameter and display its different values in different components.
We will use map to iterate through the array of objects. Following is the example code in this regard:
return (
<>
{userData.map((data,index)=>{
return <div key={index}>
<h2>{data.first_name} {data.last_name}</h2>
<p>{data.email}</p>
</div>
})}
</>
In my scenerio, the following code helped me to generically generate multiple cards on the same page using a loop (map):
{data.map((data1, id)=> {
return <div key={id} className='c-course-container-card'>
<Courses
courseid = {data1.courseid}
courselink = {data1.courselink}
img = {data1.imgpath}
coursetitle = {data1.coursetitle}
coursedesc = {data1.coursedesc}
/>
</div>
})}
Hope it helps! :)
I have a function that is used to change the state of a react component but I'm trying to pass the function in another file. I get the error that the function I'm trying to pass (changeView) is not defined.
This is the App.js
export default class App extends Component {
constructor() {
super();
this.state = {
language: "english",
render: ''
}
}
changeView(view, e){
console.log(view);
this.setState({render: view});
}
_renderSubComp(){
switch(this.state.render){
case 'overview': return <Overview />
case 'reviews': return <Reviews />
}
}
render() {
const {render} = this.state
return <Fragment>
<Header language={this.state.language} />
<Hero />
<Navigation render={render}/>
{this._renderSubComp()}
</Fragment>;
}
}
I'm trying to pass the changeView method to the Navigation.JS component, so I can change the active link as well as render the components listed in the _renderSubComp method above.
import React from "react";
import "./navigation.css";
import { changeView } from "../app";
export default function Navigation() {
return <div className="navigation">
<a onClick={this.changeView.bind(this,
'overview')}>Overview</a>
<a>Reviews</a>
</div>;
}
How should I pass the function to another file so it's able to change the state of my component and render the component I need.
You can't import a method like that. You will pass your function like any other prop to your component and you use there.
I've changed a few things. Firstly, I define changeView function as an arrow one, so we don't need to bind it. Secondly, I pass this function to the component as a prop. Thirdly, I used this function there like:
onClick={() => props.changeView('overview')}
As you can see it is props.changeView not state.changeView
Just go through the official documentation a little bit more. You are confused about states, props and how to pass them to your components.
class App extends React.Component {
constructor() {
super();
this.state = {
language: "english",
render: ''
}
}
changeView = (view, e) => {
console.log(view);
this.setState({ render: view });
}
render() {
const { render } = this.state
return <div>
<Navigation render={render} changeView={this.changeView} />
</div>;
}
}
const Navigation = (props) => {
return <div className="navigation">
<a onClick={() => props.changeView('overview')}>Overview</a>
<a>Reviews</a>
</div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<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="root"></div>
say my HOC is:
import React, { Component } from "react";
let validateURL = WrappedComponent =>
class extends Component{
render() {
if( wrappedcomponentnameis === 'xyz')
return ...
elseif(wrappedcomponentnameis === 'abc')
return ...
and so on....
}
};
export default validateURL;
how do I get the name of wrapped component inside this HOC?
You can access it via WrappedComponent.name:
const HOC = WrappedComponent => class Wrapper extends React.Component{
render() {
if (WrappedComponent.name === 'Hello') {
return <WrappedComponent name='World' />
}
return <WrappedComponent/>
}
}
class Hello extends React.Component {
render() {
return <div>Hello {this.props.name}</div>
}
}
const App = HOC(Hello)
ReactDOM.render(
<App />,
document.getElementById('container')
);
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>
However, I will prefer to pass optional props to the HOC, in order to control its behavior, because it's much safer, rather than relying on WrappedComponent.name.
For example: there are many libraries (as redux, react-router, and etc) which provide some functionality to your components through HOC mechanism. When this libraries wraps your component, then WrappedComponent.name will point to the library HOC name and will break your logic silently.
Here's how you can pass custom props:
const HOC = (WrappedComponent, props) => class Wrapper extends React.Component{
render() {
const { shouldPassName } = props
if (shouldPassName) {
return <WrappedComponent name='World' />
}
return <WrappedComponent/>
}
}
const App = HOC(Hello, { shouldPassName: true })