if else condition not evaluating in react component - javascript

So I'm making a todo list app and to provide alternate colors to new todo items as they are added in the todolist i have used the if else statement in React component but i think it is not evaluating.
Below are the two classes from my css file that i'm using -->
.bgColorItem1{
background-color: white;
}
.bgColorItem2{
background-color:grey;
}
Below is the component that accepts arguments item(todo item that will be added to the list) and key(index passed as key) from todolist -->
import React from 'react';
import './App.css';
const TodoItem=({item,key})=>{
let settingClass="";
// *****so the problem is here in the if condn that's not putting settingClass as bgColorItem1*****
if(key%2===0){
settingClass="bgColorItem1";
}else{
settingClass="bgColorItem2";
}
return <div className={`boxSpace centerAligning ${settingClass}`}>{item}</div>
}
export default TodoItem;
So what i expect from this code that key which was index in todolist component passed to todo here above should return 0 for even so that settingClass can have alternate values and hence provide alternate colors.But that is not the case.

First of all, don't use key's value since it is internal. Secondly, you can achieve like this
{items.map((item, index) => <TodoItem item={item} index={index} />)}
In TodoItem
const TodoItem=({ item, index })=>{
let settingClass=index % 2 === 0 ? 'bgColorItem1' : 'bgColorItem2';
return <div className={`boxSpace centerAligning ${settingClass}`}>{item}</div>
}
However, you don't need react to do this, just use css, in your css
.boxSpace:nth-child(odd) {
background: red;
}
.boxSpace:nth-child(even) {
background: blue;
}

You can't use key property as it is reserved, also you will get a warning for it:
Warning: ListItem: key is not a prop. Trying to access it will result in undefined being returned. If you need to access the same value within the child component, you should pass it as a different prop.
Simple example:
const ListItem = ({ key }) => {
console.log(key);
return <div>{key}</div>;
};
const App = () => {
return (
<>
{[0, 1, 2, 4].map(item => (
<ListItem key={item} />
))}
</>
);
};

Your TodoItem component shouldn't have to care about the key prop, but rather a boolean (or a string if you have more than 2 styles) prop like alternateStyle:
const TodoItem=({item,alternateStyle})=>{
return <div className={
`boxSpace centerAligning ${alternateStyle ? 'bgColorItem2' :
'bgColorItem1'}`
}>
{item}
</div>
Then you can set the value you need to alternateStyle in the parent component:
<div>
{items.map((item, index) => <TodoItem item={item} alternateStyle={index % 2 !== 0} />)}
</div>

key "prop" is reserved in React. You cannot use it. Rename this props to "idx"
See: Special props

Related

Implementing a function to swap array elements in React without mutating the original array

I am coding a react app in which a user can click a button to swap an item in an array with the item to its left. I wrote a function to implement this without mutating the original items array that is rendering on the page, but this function is not doing anything to my code, nor is it returning any errors.
Here is my app component, which defines the function swapLeft then passes that function down to the Item component as props:
import React, { useState } from "react";
import Form from "./components/Form";
import Item from "./components/Item";
import { nanoid } from "nanoid";
import './App.css';
function App(props) {
const [items, setItems] = useState(props.items);
function deleteItem(id) {
const remainingItems = items.filter(item => id !== item.id);
setItems(remainingItems);
}
function swapLeft(index) {
const index2 = index - 1;
const newItems = items.slice();
newItems[index] = items[index2];
newItems[index2] = items[index];
return newItems;
}
const itemList = items
.map((item, index) => (
<Item
id={item.id}
index={index}
name={item.name}
key={item.id}
deleteItem={deleteItem}
swapLeft={swapLeft}
/>
));
function addItem(name) {
const newItem = { id: "item-" + nanoid(), name: name };
setItems([...items, newItem]);
}
return (
<div className="form">
<Form addItem={addItem} />
<ul className="names">
{itemList}
</ul>
</div>
);
}
export default App;
And the Item component:
import React from "react";
import { Button, Card, CardContent, CardHeader } from 'semantic-ui-react'
export default function Item(props) {
return (
<Card>
<CardContent>
<CardHeader> {props.name}</CardHeader>
<Button onClick={() => props.deleteItem(props.id)}>
Delete <span className="visually-hidden"> {props.name}</span>
</Button>
</CardContent>
<CardContent style={{ display: 'flex' }}>
<i className="arrow left icon" onClick={() => props.swapLeft(props.index)} style={{ color: 'blue'}}></i>
<i className="arrow right icon" style={{ color: 'blue'}}></i>
</CardContent>
</Card>
);
}
Is there a better way for me to write this function and implement this? I suppose I could do something with the React setState hook, but this seemed like an easier solution. I am new to React so any insight would be helpful
The way React knows if the state has changed is whether the state is refers to an entirely different address in memory. In case of arrays, if you want React to rerender the page because the array in the state changed, you need to provide it an entirely new array. Modifying the existing array will not trigger the render process.
Basically, what you need to do is changed the last line of swapLeft function to
setItems(newItems)
If you want the changes to take effect immediately (which is what I guess you want to do here)
You can also use the return value from the function and change the state in another component, FYI.
EDIT:
I looked at this again, and your implementation of swap is also wrong, but even if you corrected it you still wouldn't see a change, unless you did what I mentioned above
The full correct function would be
function swapLeft(index) {
const index2 = index - 1;
const newItems = items.slice();
const temp = items[index];
newItems[index] = items[index2];
newItems[index2] = temp;
setItems(newItems);
}
Just to maybe clarify the previous one. If you don't call setState, your component doesn't rerender. This means that no matter what you do with those arrays, it won't be visible on the screen.

How can I render an array of components in react without them unmounting?

I have an array of React components that receive props from a map function, however the issue is that the components are mounted and unmounted on any state update. This is not an issue with array keys.
Please see codesandbox link.
const example = () => {
const components = [
(props: any) => (
<LandingFirstStep
eventImage={eventImage}
safeAreaPadding={safeAreaPadding}
isActive={props.isActive}
onClick={progressToNextIndex}
/>
),
(props: any) => (
<CameraOnboarding
safeAreaPadding={safeAreaPadding}
circleSize={circleSize}
isActive={props.isActive}
onNextClick={progressToNextIndex}
/>
),
];
return (
<div>
{components.map((Comp, index) => {
const isActive = index === currentIndex;
return <Comp key={`component-key-${index}`} isActive={isActive} />;
})}
</div>
)
}
If I render them outside of the component.map like so the follow, the component persists on any state change.
<Comp1 isActive={x === y}
<Comp2 isActive={x === y}
Would love to know what I'm doing wrong here as I am baffled.
Please take a look at this Codesandbox.
I believe I am doing something wrong when declaring the array of functions that return components, as you can see, ComponentOne is re-rendered when the button is pressed, but component two is not.
You should take a look at the key property in React. It helps React to identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity
I think there are two problems:
To get React to reuse them efficiently, you need to add a key property to them:
return (
<div>
{components.map((Comp, index) => {
const isActive = index === currentIndex;
return <Comp key={anAppropriateKeyValue} isActive={isActive} />;
})}
</div>
);
Don't just use index for key unless the order of the list never changes (but it's fine if the list is static, as it appears to be in your question). That might mean you need to change your array to an array of objects with keys and components. From the docs linked above:
We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. Check out Robin Pokorny’s article for an in-depth explanation on the negative impacts of using an index as a key. If you choose not to assign an explicit key to list items then React will default to using indexes as keys.
I suspect you're recreating the example array every time. That means that the functions you're creating in the array initializer are recreated each time, which means to React they're not the same component function as the previous render. Instead, make those functions stable. There are a couple of ways to do that, but for instance you can just directly use your LandingFirstStep and CameraOnboarding components in the map callback.
const components = [
{
Comp: LandingFirstStep,
props: {
// Any props for this component other than `isActive`...
onClick: progressToNextIndex
}
},
{
Comp: CameraOnboarding,
props: {
// Any props for this component other than `isActive`...
onNextClick: progressToNextIndex
}
},
];
then in the map:
{components.map(({Comp, props}, index) => {
const isActive = index === currentIndex;
return <Comp key={index} isActive={isActive} {...props} />;
})}
There are other ways to handle it, such as via useMemo or useCallback, but to me this is the simple way — and it gives you a place to put a meaningful key if you need one rather than using index.
Here's an example handling both of those things and showing when the components mount/unmount; as you can see, they no longer unmount/mount when the index changes:
const {useState, useEffect, useCallback} = React;
function LandingFirstStep({isActive, onClick}) {
useEffect(() => {
console.log(`LandingFirstStep mounted`);
return () => {
console.log(`LandingFirstStep unmounted`);
};
}, []);
return <div className={isActive ? "active" : ""} onClick={isActive && onClick}>LoadingFirstStep, isActive = {String(isActive)}</div>;
}
function CameraOnboarding({isActive, onNextClick}) {
useEffect(() => {
console.log(`CameraOnboarding mounted`);
return () => {
console.log(`CameraOnboarding unmounted`);
};
}, []);
return <div className={isActive ? "active" : ""} onClick={isActive && onNextClick}>CameraOnboarding, isActive = {String(isActive)}</div>;
}
const Example = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const progressToNextIndex = useCallback(() => {
setCurrentIndex(i => (i + 1) % components.length);
});
const components = [
{
Comp: LandingFirstStep,
props: {
onClick: progressToNextIndex
}
},
{
Comp: CameraOnboarding,
props: {
onNextClick: progressToNextIndex
}
},
];
return (
<div>
{components.map(({Comp, props}, index) => {
const isActive = index === currentIndex;
return <Comp key={index} isActive={isActive} {...props} />;
})}
</div>
);
};
ReactDOM.render(<Example/>, document.getElementById("root"));
.active {
cursor: pointer;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

How to create html element dynamically with props based on `as` on react (TypeScript)

I want to create a, b, div or another element based on as={""} prop in my function.
I am checking as and then use if guards to create element one-by-one. But is there any way to create html element based on as component?
I want to be able to create b, a, h4 h3 or any element without if's
I've found similar question but it uses styled components. Is it possible to do it without it?
Currently im doing this:
import * as React from 'react'
interface Props {
children: (React.ReactChild | React.ReactNode) | (React.ReactChild[] | React.ReactNode[])
onClick?: (e) => void
as?: string
}
const DynElement: React.FunctionComponent<Props> = props => {
const { as , children } = props
return (
<>
{as === 'div' && <div {...props_rest}>
{children}
</div>}
{as === 'b' && <b {...props_rest}>
{children}
</b>}
</>
)
}
export default DynElement
and usage is:
<DynElement as="b" onClick={...}>...</DynElement>
Using TypeScript + React.
Capture the as prop in an uppercase variable and use that as a JSX component:
const { as: Cmp = ‘div’, ...rest } = props;
return (
<Cmp {...rest} />
);
No need to even pass children explicitly. It will be passed along with the ‘rest’ props.

Component not passing props to other component in same file when calling as const

I am trying to pass props from one component to another. The index property in SortableSectionList does not seemed to be passed to SortableSection though. See console output below.
Index in SortableSectionList: 0
Index in SortableSectionList: 1
(2) Index in SortableSection: undefined
Other properties like menuSection get passed just fine though. See the full code below. Any help is appreciated, thanks!
import React from 'react'
import MenuSection from './MenuSection'
import { SortableContainer, SortableElement } from 'react-sortable-hoc'
class MenuSections extends React.Component {
render() {
const menuSections = this.props.menuSections
const SortableSectionList = SortableContainer(({ menuSections, onSectionSortEnd }) => (
<div>
{menuSections.map(function(menuSection, index) {
console.log('Index in SortableSectionList: ' + index)
return (
<SortableSection
collection="section"
key={`item-${menuSection.id}`}
menuSection={menuSection}
index={index}
menuItems={menuSection.menuItems}
onSortEnd={onSectionSortEnd}
/>
)
})}
</div>
))
const SortableSection = SortableElement(({ menuSection, index, menuItems, onSortEnd }) => {
console.log('Index in SortableSection: '+index)
return (
<MenuSection
key={menuSection.id}
menuSection={menuSection}
index={index}
menuItems={menuItems}
onSortEnd={onSortEnd}
/>
)
})
return (
<div>
<SortableSectionList
menuSections={this.props.menuSections}
lockAxis="y"
lockToContainerEdges
onSortEnd={this.props.onSortEnd}
onSectionSortEnd={this.props.onSectionSortEnd}
/>
</div>
)
}
}
export default MenuSections
It seems that react-sortable-hoc uses index property by itself. So if you want to use it also you better add another property like sortIndex or similar and pass it.
return (
<SortableSection
index={index}
sortIndex={index}
...
They also have an explanation and example in their docs.

React unique key prop

I know there are many answers out there for this issue but I couldn't find one that exactly solved my problem. I am getting the following error : Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of QuestionItem. See https://fb.me/react-warning-keys for more information.
I am setting a key for the component but I can't get the warning to go away.
Main component :
renderData() {
return this.state.data.map((data) => {
return (
<QuestionItem key={data._id} data={data} delete={this.deleteItem} edit={this.editItem} />
)
})
}
QuestionItem component :
import React, { Component, PropTypes } from 'react';
import Card from 'material-ui/lib/card/card';
import CardActions from 'material-ui/lib/card/card-actions';
import CardHeader from 'material-ui/lib/card/card-header';
import CardMedia from 'material-ui/lib/card/card-media';
import CardTitle from 'material-ui/lib/card/card-title';
import FlatButton from 'material-ui/lib/flat-button';
import CardText from 'material-ui/lib/card/card-text';
import Delete from 'material-ui/lib/svg-icons/action/delete';
import ModeEdit from 'material-ui/lib/svg-icons/editor/mode-edit';
import IconButton from 'material-ui/lib/icon-button';
import Button from '../UI/Button';
class QuestionItem extends Component {
renderTags() {
return this.props.data.tag.map((tag) => {
return (
<FlatButton label={tag} />
)
})
}
renderCompany() {
return this.props.data.company.map((company) => {
return (
<FlatButton label={company} />
)
})
}
edit = () => {
this.props.edit(this.props.data);
}
delete = () => {
this.props.delete(this.props.data._id);
console.log(this.props.data._id);
}
render() {
return (
<Card style={{margin: 50}}>
<CardTitle title={this.props.data.text} />
<CardText>
{this.props.data.answer}
</CardText>
<CardActions>
{ this.renderTags() }
{ this.renderCompany() }
<IconButton onClick={this.delete} style={{float: 'right'}}>
<Delete />
</IconButton>
<IconButton onClick={this.edit} style={{float: 'right'}}>
<ModeEdit />
</IconButton>
</CardActions>
</Card>
)
}
}
export default QuestionItem;
What am I missing here?
Well you'll need to log out the data._id and verify that they are all unique. Or you can do this:
renderData() {
return this.state.data.map((data, index) => {
return (
<QuestionItem key={index} data={data} delete={this.deleteItem} edit-{this.editItem} />
);
});
}
As the other answer pointed out, the other calls to map that go to a render need to set the key prop too to a unique value.
So these:
renderTags() {
return this.props.data.tag.map((tag) => {
return (
<FlatButton label={tag} />
)
})
}
renderCompany() {
return this.props.data.company.map((company) => {
return (
<FlatButton label={company} />
)
})
}
Should become:
renderTags() {
return this.props.data.tag.map((tag, index) => {
return (
<FlatButton key={index} label={tag} />
);
});
}
renderCompany() {
return this.props.data.company.map((company, index) => {
return (
<FlatButton key={index} label={company} />
);
});
}
Note we are using index which is the array index. It is basically like a synthetic identifier in SQL. If what you're actually rendering has unique identifiers already, it is better to use those! For example, the key prop for a tag could be just the tag -- the string itself. The key prop supports multiple types:
react - nodes-and-elements:
key : string | boolean | number | null,
So if your tags are unique (I would expect them to be but obviously don't want to assume), you could do this:
renderTags() {
return this.props.data.tag.map((tag) => {
return (
<FlatButton key={tag} label={tag} />
);
});
}
You might consider doing instead something like (tag || '').toLowerCase().replace(' ', '_') however I think React is already doing some manipulation there (besides potentially character case). So just passing the tag itself should be good! You can inspect the DOM to see data-reactid if you're not running a version that got rid of it (I think 0.15 gets rid of it). The React developer tools might let you inspect the key with 0.15.
Update
I do not recommend using the array index as the key. It causes subtle bugs. To see this in action, make an array of objects, render them using the array index and then mutate the array by removing say the 2nd element (and ensure React renders again). Now the indexes don't correspond to the same objects. My recommendation is to always set a key to a unique value. During development, it might be best not to set a key until you find one rather than using the array index because then the errors on the console will remind you to fix this before deploying/committing your change.
In renderTags() and renderCompany() you have iterators with no keys.

Categories

Resources