I want to show a div related to an object property that comes true or false.
And I used a way but I'm not sure it's the best way or is it open the performance problems.
I'm checking that property in the loop that in return section for avoid extra array operation. But I think it will cause extra render.
The other option is checking that property in outside from return section. But this will be cause an extra array operation.
Which is the best way for me? I showed 2 different implementation below.
Option 1:
const RadioButtonList: FunctionComponent<RadioButtonListProps> = ({ items, changeFilter }) => {
const [showClearIcon, setShowClearIcon] = React.useState(false);
return (
<div className="radio-button-list">
{showClearIcon && <div className="clear-icon">clear</div>}
<ul>
{items.map(item => {
/* this is the area what I'm checking the property */
if (item.selected) {
setShowClearIcon(true);
}
return (
<li key={item.id}>
<label htmlFor={item.text} className="radio">
<span className="input">
<input type="radio" onClick={changeFilter} readOnly />
</span>
</label>
</li>
);
})}
</ul>
</div>
);
};
Option 2:
const RadioButtonList: FunctionComponent<RadioButtonListProps> = ({ items, changeFilter }) => {
const [showClearIcon, setShowClearIcon] = React.useState(false);
/* set in useEffect hook */
useEffect(() => {
if(items.some(item => item.selected)) {
setShowClearIcon(true);
}
}, [items]);
return (
<div className="radio-button-list">
{showClearIcon && <div className="clear-icon">clear</div>}
<ul>
{items.map(item => {
return (
<li key={item.id}>
<label htmlFor={item.text} className="radio">
<span className="input">
<input type="radio" onClick={changeFilter} readOnly />
</span>
</label>
</li>
);
})}
</ul>
</div>
);
};
It looks like showClearIcon doesn't need to be a state atom at all, but just a memoized value dependent on items.
const showClearIcon = React.useMemo(
() => items.some(item => item.selected),
[items],
);
Option 1 enqueues a state update in the render return, don't use it.
Use option 2 to correctly enqueue the update as a side effect. In React the render function is to be considered a pure function. Don't unconditionally enqueue state updates.
Regarding performance, iterating an array is O(n). Iterating an array twice is still O(n).
Suggestion
The showClearIcon "state" probably shouldn't be React state since it's easily derived from the items prop.
Identify the Minimal (but complete) Representation of UI State
Let’s go through each one and figure out which one is state. Ask three
questions about each piece of data:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
Because of this, just compute the showClearIcon value locally.
const showClearIcon = items.some(item => item.selected);
This can be memoized with the useMemo hook with dependency on items if necessary.
You can technically do it without useState and useEffect and without iterating over the array twice, check out the example below (it might not be neccessary but it's good to know that this is possible as well):
const RadioButtonList: FunctionComponent<RadioButtonListProps> = ({
items,
changeFilter,
}) => {
const renderItems = () => {
let showClearIcon = false;
let markup = (
<ul>
{items.map((item) => {
if (item.selected) {
showClearIcon = true;
}
return (
<li key={item.id}>
<label htmlFor={item.text} className="radio">
<span className="input">
<input type="radio" onClick={changeFilter} readOnly />
</span>
</label>
</li>
);
})}
</ul>
);
return (
<>
{showClearIcon && <div className="clear-icon">clear</div>}
{markup}
</>
);
};
return <div className="radio-button-list">{renderItems()}</div>;
};
Simply created a function that generates the markup.
So, what you can do if you want to purely look at performance, is install the react dev tools chrome extension, then in your test server (for example localhost:3000) you can use the profiler tab in chrome's f12 menu to check which operation takes more memory!
this is something you can use for all optimization questions
If your goal is to render html components, option 2 (useEffect) should be used, by keeping in mind that only the required dependencies are included in the dependency array (this is to avoid re-render and performance related issues). In your case, option 2 is correct, and its performance will be tuned by React itself.
Option 2 is better approach, but Option 2 you can also have little better coding as below to make code more better and simpler
const RadioButtonList: FunctionComponent<RadioButtonListProps> = ({ items, changeFilter }) => {
// as suggested by AKX
const showClearIcon = React.useMemo(
() => items.some(item => item.selected),
[items],
);
/* set in useEffect hook */
useEffect(() => {
if(items.some(item => item.selected)) {
setShowClearIcon(true);
}
}, [items]);
// for displaying row items
const generateRow = ({id, text}) => {
return (
<li key={item.id}>
<label htmlFor={item.text} className="radio">
<span className="input">
<input type="radio" onClick={changeFilter} readOnly />
</span>
</label>
</li>
);
}
return (
<div className="radio-button-list">
{showClearIcon && <div className="clear-icon">clear</div>}
<ul>
{items.map(item => generateRow(item))}
</ul>
</div>
);
};
Related
i have an array, called reportsData, then i need to filter it, generating some checkboxes with each of them having a label based on each name that comes from another array (emittersData), so basically i set it like this:
const [searchUser, setSearchUser] = useState<string[]>([])
const mappedAndFiltered = reportsData
.filter((value: any) =>
searchUser.length > 0 ? searchUser.includes(value.user.name) : true
)
Then i render my checkboxes like this:
function EmittersCheckboxes () {
const [checkedState, setCheckedState] = useState(
new Array(emittersData.length).fill(false)
)
const handleOnChange = (position: any, label: any) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
)
setSearchUser((prev) =>
prev.some((item) => item === label)
? prev.filter((item) => item !== label)
: [...prev, label]
)
setCheckedState(updatedCheckedState)
};
return (
<div className="App">
{emittersData.map((value: any, index: any) => {
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
className="h-4 w-4 focus:bg-indigo border-2 border-gray-300 rounded"
type="checkbox"
id={`custom-checkbox-${index}`}
name={value.Attributes[2].Value}
value={value.Attributes[2].Value}
checked={checkedState[index]}
onChange={() => handleOnChange(index, value.Attributes[2].Value)}
/>
<label className="ml-3 font-medium text-sm text-gray-700 dark:text-primary" htmlFor={`custom-checkbox-${index}`}>{value.Attributes[2].Value}</label>
</div>
</div>
</li>
);
})}
</div>
)
}
And on the react component i am rendering each checkbox, that is a li, like:
<ul><EmittersCheckboxes /></ul>
And i render the mappedAndFiltered on the end.
Then it is fine, when i click each generated checkbox, it filters the array setting the state in setSearch user and the array is filtered.
You can check it here: streamable. com /v6bpk6
See that the filter is working, the total number of items in the array is changing based on the checkbox selected (one or more).
But the thing is that each checkbox does not become 'checked', it remains blank (untoggled).
What am i doing wrong, why doesnt it check itself?
You've defined your EmittersCheckboxes component inside another component. and every time that the parent component renders (by state change) your internal component is redefined, again and again causing it to lose it's internal state that React holds for you.
Here's a simplified example:
import React, { useState } from "react";
function CheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>CheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
export default function App() {
const [counter, setCounter] = useState(1);
function InternalCheckboxeComponent() {
const [checkedState, setCheckedState] = useState(false);
return (
<div>
<span>InternalCheckboxeComponent</span>
<input
type="checkbox"
checked={checkedState}
onChange={() => setCheckedState((x) => !x)}
/>
</div>
);
}
return (
<>
<InternalCheckboxeComponent />
<CheckboxeComponent />
<button onClick={() => setCounter((c) => c + 1)}>{counter}</button>
</>
);
}
There's the App (parent component) with its own state (counter), with a button to change this state, clicking this button will increase the counter, causing a re-render of App. This re-render redefines a new Component named InternalCheckboxeComponent every render.
The InternalCheckboxeComponent also has an internal state (checkedState).
And there's an externally defined functional component named CheckboxeComponent, with this component React is able to hold its own state, because it's not redefined (It's the same function)
If you set the state of each to be "checked" and click the button, this will cause a re-render of App, this will redefine the InternalCheckboxeComponent function, causing React to lose its state. and the CheckboxeComponent state remains in React as it's the same function.
so what I am trying to achieve here is storing a whole component in an array in a parent component which renders a specific component in the array using its index for example :
export const Test = () => {
const [components, setComponents] = useState([
<Order key={1} />,
<Order key={2} />,
<Order key={3} />,
]);
const [index, setIndex] = useState(0);
return (
<div>
<button onClick={() => setIndex((old) => (old + 1) % components.length)}>
change
</button>
{`page ` + index}
{components[index]}
</div>
);
};
const Order = () => {
const [someState, setSomeState] = useState(1);
return (
<div>
<button onClick={() => setSomeState((old) => old + 1)}>
{someState}
</button>
</div>
);
};
when I change the state of one item then cycle through the items then return to the item which I changed its state i found that it is not updated
what I figured out is that the component in the array (in the Test component) doesn't get updated and I couldn't figure out how to update it
what I don't want to do is storing the state of the order item in the parent and pass it as props (because it will be a pain to make it work)
const App = ({ flag }) => {
if (flag) <Order />
return null
}
I'm giving you an example so i can explain what might happen in your case. If the flag becomes false from a true, the App turns blank. But what happen to the Order? It's unmounted, why? Since when React compares between the previous scene and the current scene, it notice there's no such Order any more. So what you think about the memory of component of Order (which is called a fiber)?
I guess the answer is, the memory goes to be deleted and will be collected for future use.
Now back to your case, you are using an id to switch to different component. But in theory it should behave very similar to my example for each component.
NOTE: the take away is that if you want to use an array, that's fine, but all components has to be rendered at ALL time, you can hide them, but you can't unmount any of them.
what I don't want to do is storing the state of the order item in the
parent and pass it as props (because it will be a pain to make it
work)
Your problem is that when you render a Test component and then increase index, then you render another Test component with a different key, so reacts reconciliation algorithm unmounts the old one and you lose the state.
You have two options:
lift state of each Test component up, then when one gets unmounted, you will remount it with the old state, because state will be stored in parent, it will not be lost
another option is to render all components and only show those which you want using CSS display property, this way none of them gets unmounted and you retain state. Here is example:
const Order = () => {
const [someState, setSomeState] = React.useState(1);
return (
<div>
<button onClick={() => setSomeState((old) => old + 1)}>
{someState}
</button>
</div>
);
};
let components = [<Order />, <Order />, <Order />];
const Test = () => {
const [index, setIndex] = React.useState(0);
return (
<div>
<button onClick={() => setIndex((old) => (old + 1) % components.length)}>
change
</button>
{`page ` + index}
{[0, 1, 2].map((x) => (
<div key={x} style={{ display: index === x ? "block" : "none" }}>
{components[x]}
</div>
))}
</div>
);
};
ReactDOM.render(
<Test />,
document.getElementById("react")
);
<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>
<div id="react"></div>
PS I have removed components from state, I can't find official info now, but IMHO it is not good idea to store components in state.
Currently I've got a react component that looks like this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
return (
<div key={i} className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div>
);
});
};
Based on the cards array, it renders something like this:
Rendered Component
Whenever I click the trash button, I make a request to my backend, edit the list on my database and rerender the component based on the now updated "cards". The problem is that this takes sometime to happen, so i wanted a way to remove it from the dom instantly while my backend does it's job.
somehting like
{show ? renderCompoennt : null}
I've tried using vanilla javascript to grab the parent from the trash can, which would be the card i want to remove, but the results are unpredictable and it's quite slow as well.
My latest try was this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
const [show, setShow] = useState(true);
return (
<div key={i}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
});
};
but react won't let me do this. Even tho its fast, everytime one item gets deleted, react complains that "less hooks were rendered" and crashes the app.
You are attempting to do some Optimistic UI, in which you assume that your action will succeed, and reflect the expected/assumed state instantly, before the request to the backend completes. This would be in lieu of showing some progress/busy indicator, like a spinner, until the action completes with the server.
The first problem and immediate problem in your code-- it violates the rules of hooks, which state that hooks may only be used at the top-level (never inside loops, conditionals, etc).
The second problem is that you are leveraging vanilla JS to manipulate the DOM directly; this generally an antipattern in MV* frameworks, and very much so here. Instead, I would suggest doing managing it in your data model; something like this:
Rewrite your .map handler to return null if the card has a deleted property.
When the user clicks the trash button, do two things:
Make the request to the backend to delete it
Use a setState to add a deleted: true property to the clicked card
Now you will get a rerender that will omit the deleted card, and also make the request to the backend, all inside the React data model. Make sure that you handle complexity for:
How to handle the response
How to handle an error if the deletion fails at the backend
How to manage if a user quickly clicks many cards for deletion before any of the requests can complete.
The problem is that in the first render you have {cards.length} calls to hook "useState" within GeraCard, but after deletion of one card, you will have {cards.length-1} calls to hook "useState". As the React docs state:
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function. By
following this rule, you ensure that Hooks are called in the same
order each time a component renders. That’s what allows React to
correctly preserve the state of Hooks between multiple useState and
useEffect calls.
You should extract the content of map callback into separate a component.
const GeraCards = (cards, cart = false) => {
return cards.map((v, i) =>
<GeraCard card={v} index={i} cart={cart} />
);
};
const GeraCard = ({ card, index, cart }) => {
const [show, setShow] = useState(true);
const v = card;
return (
<div key={index}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
}
I am relatively new to ReactJs.I am learning react while I am trying to create a real world app. Here is something I cannot solve.
I have a repeated component that has one input and one button.
everytime the button is clicked, the value of the input will be used in one function.
In Angular I do not have to worry about how to passing those value since in ngFor we can directly assign the value from the ngModel. But there is no such concept in React.
betOnTeam = (_id, coins) => {
return;
};
{this.teamList.map(team => (
<div key={team._id}>
<input type="number" min="100" max="5000" />
<button type="button"
onClick={() => this.betOnTeam(team._id,//value from the
input above)}>
</div>
))}
So basically I Have a function ready to receive an Id and how many coins the user bet.
And As we can see from the picture, I have many inputs which should contain the value of how much coins the user put for a specific team.
each button will trigger this betOnTeam function and will pass the unique Id of the team, and the number coins the user bet.
How can I set states for all thoese teams since they are all dynamic, it could be 5 teams or 100 teams. Is it any way to do it dynamically?
e.g. user input 5000, when he click the button, the id and the value will be passed into the function betOnTeam.
I hope this clarified my question.
==================================
Thanks for all the input from you guys.
I have make it working combine with all your suggestions.
So Here is what I do:
betOnTeam = (event, id) => {
console.log(event.target[0].value, id);
return;
};
{this.teamList.map(team => (
<form key={team._id} onSubmit={(e) => this.betOnTeam(e,team._id)}>
<input type="number" min="100" max="5000" />
<button type="submit">
</form >
))}
Seems like you're really close. I think this ultimately comes down to how you want to construct your components. There is an easy way to do this (the more React) way, and there is a hard way.
The easy way is to split the mark-up created inside the .map() into its own component. You will have an individual component for each team, thus the state is encapsulated to its own component. By doing this you can effectively keep track of the inputs for each team.
Consider this sandbox for example: https://codesandbox.io/s/dazzling-roentgen-jp8zm
We can create a component for the markup like this:
Team
import React from "react"
class Team extends React.Component {
state = {
betValue: 100
};
handleOnChange = e => {
this.setState({
betValue: e.target.value
});
};
handleOnClick = () => {
this.props.betOnTeam(this.state.betValue, this.props.id);
};
render() {
const { id } = this.props;
const { betValue } = this.state;
return (
<div key={id}>
<input
type="number"
min="100"
max="5000"
value={betValue}
onChange={this.handleOnChange}
/>
<button onClick={this.handleOnClick} type="button">
Bet
</button>
</div>
);
}
}
export default Team;
So from a purely jsx standpoint, the markup is the same, but now it is contained inside a class-component.
Now we can keep track of the inputs in a controlled manner.
When we're ready to place the bet, the value is stored in the
individual component state.
We pass down two properties to each Team component, the team_id and
betOnTeam function. The team_id can be accessed using this.props.id and likewise we will pass it into this.props.betOnTeam() when required.
Main Component
import React from "react"
import Team from "./Team"
class App extends React.Component {
teamList = [
{ team_id: 1, name: "TSM" },
{ team_id: 2, name: "SKT" },
{ team_id: 3, name: "CLG" }
];
betOnTeam = (betValue, teamId) => {
console.log(betValue);
console.log(teamId);
};
render() {
return (
<div>
{this.teamList.map(team => (
<Team id={team.team_id} betOnTeam={this.betOnTeam} />
))}
</div>
);
}
}
So the .map() renders a Team component for each team and passes in their respective ids and the betOnTeam function as props. When the button inside the component is clicked, we can pass back up the values stored in the Team Component to execute betOnTeam.
onClick={this.betOnTeam(form._id,value)}
Don't execute this.betOnTeam right from the start, you're actually setting the click handler to the returned result of this.betOnTeam(form._id,value). In React, it's not quite the same as in Angular. For this, you need to set it equal to a function that does that. Like this:
onClick={() => this.betOnTeam(form._id,value)}
Hope this helps.
1. this.betOnTeam = (_id, value) => { ... }
2. constructor(props) { this.betOnTeam.bind(this) }
3. onClick = { () => this.betOnTeam(form._id, value)}
Well if you use onClick = { this.betOnTeam(form._id, value) }, then the code will be executed first, and in betOnTeam function, you will not use 'this' operator.
But if you use the above methods, then you can use 'this' in the function and get the good results.
And your code has some bugs to fix,
{this.array.map(form => (
<div key={form._id}>
<input name={`editform{form._id}`} type="number" min="100" max="5000" onChange={(e) => this.changeNumber(e, form._id) }/>
<button type="button"
onClick={this.betOnTeam(form._id,value)}>
</div>
))}
And in changeNumber function, you should use setState({}) function to set the value to the state, and in betOnTeam function, you can use the state you have already set.
The code must be like this, or otherwise you can use ref but it is not formally encouraged to use ref.
Totally, you should use ControlledComponent. That's the target.
I hope you to solve the problem.
I have a child component that is supposed to display names based on a visibility filter with checkboxes. I use a dictionary to keep track of the checked state for each name. However, when I update the dictionary the child does not update.
Here is an example: https://codesandbox.io/s/8k39xmxl52
These are the components:
const App = () => {
const [names, setNames] = useState(seedNames);
const [hidden, setHidden] = useState(new Map());
const handleHidden = e => {
const name = e.target.name;
const hidden = e.target.checked;
setHidden(hidden.set(name, hidden));
};
return (
<div className="App">
<VisibilityFilter
names={names}
hidden={hidden}
handleHidden={handleHidden}
/>
<DisplayNames names={names} hidden={hidden} />
</div>
);
};
const VisibilityFilter = ({ names, hidden, handleHidden }) => {
return (
<div>
{names.map(name => (
<div key={name}>
<input
type="checkbox"
name={name}
checked={hidden.get(name)}
onChange={handleHidden}
defaultChecked
/>
<span>{name}</span>
</div>
))}
</div>
);
};
const DisplayNames = ({ names, hidden }) => {
const visibleNames = names.filter(name => !hidden.get(name));
return (
<div>
{visibleNames.map(name => (
<div key={name}>{name}</div>
))}
</div>
);
};
The use of immutable state is idiomatic to React, it's expected that simple types like plain objects and arrays are used.
Map set returns the same map, while useState supports only immutable states. In case setter function is called with the same state, a component is not updated:
setHidden(hidden.set(name, hidden))
The state should be kept immutable:
setHidden(new Map(hidden.set(name, hidden)))
This may introduce some overhead and defy possible benefits of ES6 Map. Unless Map features like strict item order and non-string keys are in demand, it's preferable to use plain object for the same purpose.