Problem: How to Render component many times, when i give input value in input box (Eg : If i enter 5 in input box Hello World Should render 5 times
React Interview Question .
This is pretty straight-forward and there are loads of ways about going about this.
Here's just one: new Array(number).fill(<Component />)
As with any of possibilities, you the developer, will have to justify various decisions.
I have opted to fill a new Array(n) of length n.
I would need some kind of error checking to stop users entering dumb values ("" would be such a value)
Other error: checking
min - guard against negative values (perhaps max would also be logical)
step - guard against decimal values (how could one get 2.5 elements after all?!)
type="number" - guarding against unparsable inputs like strings
// Get a hook function
const {useState} = React;
const Component = () => {
return <div>Hello World</div>
}
const Example = () => {
const [howMany, setHowMany] = useState(1);
const allComponents = new Array(howMany).fill(<Component />)
return (
<div>
<input type="number" step={1} min={0} value={howMany} onChange={(event) => {
const newValue = event.target.value
const parsedValue = parseInt(newValue)
if (parsedValue < 0 || isNaN(parsedValue)) {
console.warn("Cannot set");
// Do some error handling
return
}
console.log("setting", parsedValue)
setHowMany(parsedValue)
}} />
<div>
{allComponents.map((elem, index) => (
<React.Fragment key={index}>
{elem}
</React.Fragment>
))}
</div>
</div>
);
};
// Render it
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Example />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Related
I m calling the component like this
<div className="sidebar__chats">
<SidebarChat addNewChat />
i m expecting all avatars except the first one that should say "add new chat". this is the code of the component
function SidebarChat(addNewChat) {
const [seed, setSeed] = useState("");
useEffect(() => {
setSeed(Math.floor(Math.random() * 5000));
}, []);
const createChat = () => {
const roomName = prompt("please enter the name for chat");
if (roomName) {
//do some clever database stuff
}
};
return (
<>
{!addNewChat ? (
<div className="sidebarChat">
<Avatar
alt="João Espinheira"
src={`https://avatars.dicebear.com/api/pixel-art/${seed}.svg`}
sx={{ width: 38, height: 38 }}
/>
<div className="sidebarChat__info">
<h2>room name</h2>
<p>last message...</p>
</div>
</div>
) : (
<div onClick={createChat} className="sidebarChat">
<h2>add new chat</h2>
</div>
)}
</>
);
}
export default SidebarChat;
can anyone help, i think this should work but not entereing the else condition. does anyone knows why? when i dont use the " ! " in the addnewchat every single one turn into avatars, and doesnt do the else statement. i dont understand why cant i use like this, since the code is ok
currently i have this outcome
image1, and it should be something like this image2
Change the first line of the component to
function SidebarChat({addNewChat}) {
The argument to a component is an object containing all the props, so you need to destructure it to access a given prop.
Let's say I have Input component, I'd like to optionally receive one Button component, one Label and/or one Icon component. Order is not important.
// Correct use case
<Input>
<Button />
<Label />
<Icon />
</Input>
// Correct use case
<Input>
<Button />
</Input>
// Incorrect use case
<Input>
<Button />
<Button />
</Input>
Does anyone know what typescript interface would solve this problem?
Real use case:
<Input
placeholder="Username"
name="username"
type={InputType.TEXT}
>
<Input.Label htmlFor={"username"}>Label</Input.Label>
<Input.Max onClick={() => console.log("Max clicked!")} />
<Input.Icon type={IconType.AVATAR} />
<Input.Button
onClick={onClickHandler}
type={ButtonType.GRADIENT}
loading={isLoading}
>
<Icon type={IconType.ARROW_RIGHT} />
</Input.Button>
<Input.Status>Status Message</Input.Status>
<Input.Currency>BTC</Input.Currency>
</Input>
I have a dynamic check that would throw an error if anything but listed child components is being used (all of possible options are presented), but I'd like to somehow include the Typescript as well and show type errors whenever somebody uses child component of different type or duplicates a component.
Looking at your code and question, the key factors can be regarded as these:
allow only one occurrence for each Button/Icon/Label component.
use JSX bracket hierarchy with given specific elements; use React.Children API.
use typescript.
These requests are contradicting at the moment. Because with typescript, it's really difficult to implement permutable array type. Here's two way of solving it, each highlights some of the key favors.
Solution 1. If the tool is not restricted to typescript, React's top level Children API can easily solve this problem.
const Button = () => {
return <button>button</button>;
}
const Label = () => {
return <label>label</label>
}
const Icon = () => {
return <div>icon</div>
}
const WithSoleChild = (props) => {
// used top-level react child API
// https://reactjs.org/docs/react-api.html#reactchildren
const childNames = React.Children.map(props.children, c => c.type.name);
// count the duplication
const childDupes = childNames.reduce((ac, cv) => ({ ...ac, [cv]: (ac[cv] || 0) + 1 }),{})
// same child should not be duplicated
const childValid = Object.values(childDupes).some(amount => amount < 2);
if (!childValid) {
// added stack trace for better dev experience.
console.error('WithSoleChild: only one Button/Icon/Label is allowed', new Error().stack);
// disable render when it does not fit to the condition.
return null;
}
return props.children;
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<div>
{/* this does not render, and throws an error to console */}
<WithSoleChild>
<Button />
<Button />
</WithSoleChild>
{/* this does render */}
<WithSoleChild>
<Button />
<Icon />
<Label />
</WithSoleChild>
</div>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
Solution 2. However, there is a more graceful solution, which works perfectly in typescript too; it is called slot component approach, also known as containment component in the official React document.
// App.tsx
import React from "react";
// extend is necessary to prevent these types getting regarded as same type.
interface TButton extends React.FC {};
interface TIcon extends React.FC {};
interface TLabel extends React.FC {};
const Button: TButton = () => {
return <button className="button">button</button>;
};
const Icon: TIcon = () => {
return <div>icon</div>;
};
const Label: TLabel = () => {
return <label>label</label>;
};
interface IWithSoleChild {
button: React.ReactElement<TButton>;
icon: React.ReactElement<TIcon>;
label: React.ReactElement<TLabel>;
}
const WithSoleChild: React.FC<IWithSoleChild> = ({ button, Icon, label }) => {
return null;
};
function App() {
return (
<div className="App">
<WithSoleChild button={<Button />} icon={<Icon />} label={<Label />} />
</div>
);
}
I'm playing with the new useTransition() hook added in React 18.
I created an app highlighting the matching names. If it matches, the item gets green, otherwise red. Here how I did it:
import { useState, useTransition } from 'react';
import people from './people.json'; // 20k items
const PeopleList = ({ people, highlight, isLoading }) => {
return (
<>
<div>{isLoading ? 'Loading...' : 'Ready'}</div>
<ol>
{people.map((name, index) => (
<li
key={index}
style={{
color: (
name.toLowerCase().includes(highlight)
? 'lime'
: 'red'
)
}}
>
{name}
</li>
))}
</ol>
</>
);
};
const App = () => {
const [needle, setNeedle] = useState('');
const [highlight, setHighlight] = useState('');
const [isPending, startTransition] = useTransition();
return (
<div>
<input
type="text"
value={needle}
onChange={event => {
setNeedle(event.target.value);
startTransition(
() => setHighlight(event.target.value.toLowerCase())
);
}}
/>
<PeopleList
people={people}
highlight={highlight}
isLoading={isPending}
/>
</div>
);
};
export default App;
The JSON file contains an array of 20k people. Unfortunately, the useTransition() doesn't seem to improve performance in this case. Whether I use it or not, typing in the input is very laggy, making about a 0.5s delay between each character.
My first thought was that maybe such a big DOM tree would cause a delay in the input, but it doesn't seem the case in a raw HTML page.
Why doesn't useTransition() make typing smooth in my example?
The reason is because your app is running in React 17 mode.
Source: https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#deprecations
react-dom: ReactDOM.render has been deprecated. Using it will warn and run your app in React 17 mode.
How about wrapping PeopleList component in a memo.
As far as my understanding goes. Everytime when setNeedle is being called <App/> component is rerendering and then <PeopleList/> is also rerendering even though nothing changed in <PeopleList/> component.
I am learning about states in React, and below is an exercise to create a to-do list.
I capture the state of each inputted "item" in the to-do list and add them to an "items" array.
Then, each element in the "items" array is rendered using HTML.
I don't understand why I can't just push each item into a normal JavaScript array? After submitting the form, I log the "items" array. However, the "items" array only has 1 element, no matter how many times I submit the form. It seems like previous values are disappearing every time.
I solved the problem by using a React state that is an array. But I still don't understand why I can't use a JS array. Any explanations/resources would be greatly appreciated!
function App() {
const items = [];
//const [items, setItems] = React.useState([]);
const [inputText, setInputText] = React.useState("");
function handleSubmit(event) {
items.push(inputText);
console.log(items);
//setItems((prevValue) => {
// return [...prevValue, inputText];
//});
event.preventDefault();
}
function handleChange(event) {
setInputText(event.target.value);
}
return (
<div className="container">
<div className="heading">
<h1>To-Do List</h1>
</div>
<form className="form" onSubmit={handleSubmit}>
<input type="text" value={inputText} onChange={handleChange} />
<button type="submit">
<span>Add</span>
</button>
</form>
<div>
<ul>
{items.map((item) => {
return <li>{item}</li>;
})}
</ul>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
You have to define the let items = []; outside the function. Because in react each component reloaded on state update. so it will re-assigned to empty array. Make sure you define it using let not const
You can use the react hook useCallback to prevent items re-rendering every time state changes const items = useCallback([], [])
I am mapping through an array of objects and displaying their values in a card, each object has a title, comment, and datePosted field. The datePosted field is put throught a function I made to display it on the card as 'Posted 2 hours ago' or 'Posted '2 minutes ago'. This is updating though everytime the state changes, which isn't a problem when it was posted days ago, or even hours, as nothing changes, but when a new post is first uploaded and saved, the date being displayed updates practically everytime you type a key, as it goes from 'Posted 1 second ago' to 'Posted 2 seconds ago', to 'Posted 3 seconds ago' and so on... . Is there anything I can do to get around this, I've considered saving the component in a different state and displaying that, but I don't think that will work as the state needs to be updated everytime the user changes a title or comment so it can be displayed in real time.
Here's an example of the JSX that is displaying the card in case it helps. timeCalc is the function that calculates how long ago the post was created
{posts.map(item => (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
<h3>{timeCalc(item.datePosted)}</h3>
</div>
</div>
))}
I would extract the code to its own component, and then that component can use one of several possible techniques to remember the value from its first render. Here's one with useMemo:
{posts.map(item => <Post item={item}/>)}
//... elsewhere:
const Post = ({ item }) => {
const time = useMemo(() => {
return timeCalc(item.datePosted);
}, []);
return (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
<h3>{time}</h3>
</div>
</div>
);
}
Other possible ways to remember the initial value are with state:
const Post = ({ item }) => {
// Note that i have no need for `setTime`, as this value will never be changed
const [time] = useState(timeCalc(item.datePosted));
Or with a ref:
const Post = ({ item }) => {
const timeRef = useRef(timeCalc(item.datePosted));
// ...
<h3>{timeRef.current}</h3>
You can create a component and memoize it using React.memo and areEqual function to avoid re-rendering when props i.e. date are same.
Demo:
const inputDate = new Date().toISOString();
function timeCalc(date) {
return moment(date).fromNow()
}
function TimeCalc({date}) {
return <h4>{timeCalc(date)}</h4>
}
function areEqual(prevProps, nextProps) {
return prevProps.date === nextProps.date
}
const TimeCalcMemo = React.memo(TimeCalc, areEqual);
function App() {
const [v, sv] = React.useState('')
return (<div>
<div>{v}</div>
<div>Enter something after a minute</div>
<input type="text" value={v} onChange={e => sv(e.target.value)} />
<TimeCalcMemo date={inputDate}/> memoized
<TimeCalc date={inputDate}/> not memoized
</div>)
}
ReactDOM.render(<App />, document.getElementById('mydiv'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<body>
<div id="mydiv"></div>
</body>
You can also try conditional rendering.
const lastUpdated = timeCalc(item.datePosted)
{posts.map(item => (
<div>
<div>
<h1>{item.title}</h1>
<h3>{item.comment}</h3>
</div>
<div>
{
(compare timestamp here using moment.js or anyway you prefer) &&
<h3>{timeCalc(item.datePosted)}</h3>
}
</div>
</div>
))}