Unexpected behaviour with rendering a list of buttons in react - reactjs

I am trying to generate a list of buttons using this method. I rewrote it as a test instance and the behaviour is the same - when you render the buttons, the function in onClick is called and cannot be called again by clicking the generated buttons. In this case 1 to 5 are logged.
function App() {
const [buttonList, setButtonList] = useState();
const experimental = async (e) => {
const array = [1,2,3,4,5];
const listItems = array.map((item) =>
<li key={item}>
<button onClick={console.log(item)}>This is a test</button>
</li>
);
setButtonList(listItems);
}
return (
<div className="App">
<header className="App-header">
<button onClick={experimental}>Click to render</button>
<ul>{buttonList}</ul>
</header>
</div>
);
}
Could anyone explain this behaviour to me and/or offer a way of doing this which doesn't do this?

Due to context this. It's must be () => {}. Let's use function and useCallback hook instead.

Related

reactstrap, callback hook used as ref weird behaviour

While trying to get a DOM element's position on render using this code:
const Modes = () => {
const callbackRef = useCallback(domNode => {
if (domNode) {
let rect = domNode.getBoundingClientRect()
console.log("rect", rect)
}
}, []);
return <>
<Toast>
<ToastHeader></ToastHeader>
<ToastBody>
<div ref={callbackRef}> </div>
</ToastBody>
</Toast>
</>
}
I noticed that it always prints a DOMRect object with zero for every property :
If I add state dependence and then state changes causing rerender, the correct position will be printed. Something like this:
const Modes = () => {
const callbackRef = useCallback(domNode => {
if (domNode) {
let rect = domNode.getBoundingClientRect()
console.log("rect", rect)
}
}, []);
const [show, setShow] = useState(true) // added state
return <>
<Toast>
<ToastHeader></ToastHeader>
<ToastBody>
{show ? <div ref={callbackRef}> </div> : null} // div inside Toast can be toggled
</ToastBody>
</Toast>
<Button onClick={() => setShow(!show)} >toggle </Button> // added toggle button
</>
}
After double click on the button:
What confuses me the most is the fact that if I replace this Toast imported from Reactstrap with pure html with bootstrap classes the problem disappears. And this is exactly what React renders because I copied it from source code in the browser:
<div class="toast fade show" role="alert">
<div class="toast-header">
<strong class="me-auto"></strong>
</div>
<div class="toast-body">
<div ref={callbackRef}> </div>
</div>
</div>
And it seems to be a problem that exists just for this Toast component. For Reactrstrap's Card for example it is not the case. So how can using a component which at the end of the day gets rendered into a certain html code be different from using the same html code and why this particular component turns out to be a special case regarding obtaining its DOMRect?

How to create a div on click of a button in react?

I've just started learning React. I'm working on a project for practice. It has two different components Nav & Main.I'm calling these components in App.js. My Nav component has a number of coloured divs which show up on clicking the plus icon. Now, I want to create a new div in Main.js on click of a coloured div in Nav.js. This new div should have the same background colour as the one we click on. I'm confused how to do it. Please! Help me out.
This is my Nav component.
const Nav = () => {
const [showColors, setShowColors] = useState(false);
return(
<div className="NavBar">
<h1 className="NavHeading">Notes.</h1>
<div onClick={() => setShowColors(!showColors)} className="PlusImg"></div>
<div className={showColors ? "" : "ColorBar"}>
<div className="Color Color1"></div>
<div className="Color Color2"></div>
<div className="Color Color3"></div>
<div className="Color Color4"></div>
<div className="Color Color5"></div>
</div>
</div>
);
};
export default Nav;
This is my Main component.
const Main = () => {
return(
<div className="Main">
<div className="NoteItem"></div>
</div>
);
};
export default Main;
I want to create new div with class name of NoteItem as the one written in the Main component.
I'm not 100% sure I understand what you're trying to do. Apologies if I've misunderstood.
I'd approach this by keeping track of the items that have been added in component state:
const App = () => {
// starting with an empty array
const [items, setItems] = React.useState([]);
And defining a handler for adding an item:
const App = () => {
const [items, setItems] = React.useState([]);
// itemClass is just a string, e.g. "color1" or "color5"
const addItem = itemClass => setItems([...items, itemClass]);
The spread syntax used above creates a new array containing the previous array plus the new item:
const oldArr = ['color1','color2'];
const newArr = [...oldArr, 'color3']
// newArr is now ['color1', 'color2', 'color3'];
You can then pass the items to Main as a prop, which can render a div for each item:
const App = () => {
const [items, setItems] = React.useState([]);
const addItem = itemClass => setItems([...items, itemClass]);
return (
<Main items={items} /> {/* pass items array to Main */}
);
}
const Main = ({items}) => { // {items} is the equivalent of props.items
// render a div for each item in the items array
return items.map( item => (
<div className={item}>This item has a class of {item}</div>
)
}
And pass the addItem handler to Nav so it can tell App to add the clicked item:
const Nav = ({addItem}) => {
return (
<div className={showColors ? "" : "ColorBar"}>
<div onClick={() => addItem('Color1')} className="Color Color1"></div>
{/* repeat for Color2, etc. */}
</div>
);
}
With this place, when a Nav div is clicked an item will get added to App's items array, which will trigger a re-render, passing the updated array to Main, and you'll see the new div.
a bit of housekeeping:
In the Main component above you're going to get a react key warning. I omitted the key in the interest of readability, but you'll need to include a key prop that's unique for each item. The easiest way to do this is to just use the index of the iteration:
const Main = ({items}) => {
// add key={index} to make react happy
return items.map((item, index) => (
<div key={index} className={item}>This item has a class of {item}</div>
)
}
Also, the items.map call will blow up if items isn't provided. You can dodge this by setting it to an empty array by default:
const Main = ({items = []}) => {
// ...
}
First of all you need to define your state in your App component and pass it to your components as an argument because both your Nav and Main components needs to access your state. You can do it like this
class App extends React.Component{
constructor(){
super()
this.state = {
showColors: false
}
}
}
After that you can take elements color with the onClick event listener. Define it in your state as well. And create a div element inside your Main component with it.
Dont forget to pass it as an argument to your Main component too.

React wrapper component (HOC) does not re-render child component when it's props are changing

My wrapper component has this signature
const withReplacement = <P extends object>(Component: React.ComponentType<P>) =>
(props: P & WithReplacementProps) => {...}
Btw, full example is here https://codepen.io/xitroff/pen/BaKQNed
It's getting original content from argument component's props
interface WithReplacementProps {
getContent(): string;
}
and then call setContent function on button click.
const { getContent, ...rest } = props;
const [ content, setContent ] = useState(getContent());
I expect that content will be replaced everywhere (1st and 2nd section below).
Here's the part of render function
return (
<>
<div>
<h4>content from child</h4>
<Component
content={content}
ReplaceButton={ReplaceButton}
{...rest as P}
/>
<hr/>
</div>
<div>
<h4>content from wrapper</h4>
<Hello
content={content}
ReplaceButton={ReplaceButton}
/>
<hr/>
</div>
</>
);
Hello component is straightforward
<div>
<p>{content}</p>
<div>
{ReplaceButton}
</div>
</div>
and that's how wrapped is being made
const HelloWithReplacement = withReplacement(Hello);
But the problem is that content is being replaced only in 2nd part. 1st remains untouched.
In the main App component I also replace the content after 20 sec from loading.
const [ content, setContent ] = useState( 'original content');
useEffect(() => {
setTimeout(() => {
setContent('...too late! replaced from main component');
}, 10000);
}, []);
...when I call my wrapped component like this
return (
<div className="App">
<HelloWithReplacement
content={content}
getContent={() => content}
/>
</div>
);
And it also has the issue - 1st part is updating, 2nd part does not.
It looks like you are overriding the withReplacement internal state with the external state of the App
<HelloWithReplacement
content={content} // Remove this to stop overriding it
getContent={() => content}
/>
Anyway it looks weird to use two different states, it is better to manage your app state in only one place

Switch between divs when clicking prev/next buttons using React Hooks

First of all, I am really new to React, so there is a lot of stuff I have no idea about. But trying to learn I've come across Hooks, which seems really nice for a noob like me. However, my next "project" I am not quite sure about.
Basically I have 4 divs of the same size, but with different content. What I would like to do is to start at the first div (rest not visible), and then in that container I have a "Next" button, if I click that it changes/switches to the second div, which then has a "Prev" and "Next" button, and so on.
I have no idea if that is even possible with Hooks using useState or something like that.
EDIT with example:
So hopefully this illustrates my idea. And this was my initial idea. I think I may be stuck at the return where I am not sure how to actually insert that into the HTML, or what you call it in React-lingo.
const App = ( ) => {
const initialCount = 0
const [count, setCount] = useState(initialCount)
if (count = 0) {
<div className="box">
<p>DIV 1</p>
<div className="btn" onClick={ () => setCount(count + 1) } >Next</div>
</div>
} else if (count = 1) {
<div className="box">
<p>DIV 2</p>
<div className="btn" onClick={ () => setCount(count - 1) } >Prev</div>
<div className="btn" onClick={ () => setCount(count + 1) } >Next</div>
</div>
}
return (
<div>
{one of the if-statement elements}
</div>
);
};
export default memo(App);
In order to understand this you need to understand how react works, in general to draw/insert HTML elements you must either return it from your function component, or use render class method in your Class component, putting HTML somewhere else will not render them to the page
As for how to achieve this, there are many ways, the simplest for me would be define a set of components you want to render in an array then render them according to the current count number
const App = () => {
const [count, setCount] = React.useState(0);
const components = [
<div>1</div>,
<div>2</div>,
<div>3</div>,
<div>4</div>
]
return <div>
{
// render component from our components array
components[count]
}
{/* show previous button if we are not on first element */}
{count > 0 && <button onClick={() => setCount(count - 1)}>prev</button>}
{/* hide next button if we are at the last element */}
{count < components.length - 1 && <button onClick={() => setCount(count + 1)}>next</button>}
</div>
}

Create a custom Hook for splitting strings (React JS)

I'm working with React JS (hooks) for recently. For a project, I need to split many strings in different divs. So, I created a function to this, that saves me some time! My actual question is : Should I create a custom Hook instead of my function ?
Of course, the actual code works, but as a beginner, I don't know if my way is clean. I need feedbacks cause my main goal to write the best and clear code as possible.
// Splitting Texts
const splitText = str => {
return (
<div>
{str.split(' ').map((item, index) => {
return (
<div key={index}>
{item}
</div>
);
})}
</div>
);
};
// Main App
export default function App() {
const name = splitText('Lucie Bachman');
const description = splitText('Hey, this is my first post on StackOverflow!');
return (
<div className="App">
<h1>{name}</h1>
<h2>{description}</h2>
</div>
);
}
Expected results :
<h1>
<div>
<div>Lucie</div>
<div>Bachman</div>
</div>
</h1>
I'm super excited to have joined the community!
Thanks to you, and take care.
Lucie Bachman
A custom hook is something that uses out of the box react hooks to actually provide a logic and return data.
If the function returns JSX, its actually just a function or can be used as a functional component
Since you only want to split string once you can convert it into a component and use React.memo to optimize rendering
// Splitting Texts
const SplitText = React.memo(({str}) => {
return (
<div>
{str.split(' ').map((item, index) => {
return (
<div key={index}>
{item}
</div>
);
})}
</div>
);
});
// Main App
export default function App() {
return (
<div className="App">
<h1><SplitText str={'Lucie Bachman'} /></h1>
<h2><SplitText str={'Hey, this is my first post on StackOverflow!''} /></h2>
</div>
);
}

Resources