Code like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const MenuItems = () => {
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
return (
renderMenuItems()
)
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{menuitems && <MenuItems/>}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
When the input tag is used, the variable MenuItems is reloaded. What's wrong in my code? Why is it rerendering and how to prevent this from happening?
As far as I understand, this happens after setting the variable "searchTi" through the function "setSearchTic". This updates the variable "menuitems " and reloads this section of code:
{menuitems && <MenuItems/>}
you are using MenuItems like it was a component, but it's only a render function. should just call it like this:
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
const renderMenuItems = () => {
if (menuitems && menuitems.length){
console.log("Render")
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
return null;
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
Compact example:
Also, there's no need to check to the menuitems.length. Best way to render the menu items would be something like this:
const renderMenuItems = () => menuitems?.map((name) => <button key={name}>{name}</button>);
useMemo:
If you want to avoid re-render the menu items over and over, you should also use React.useMemo like this:
const renderMenuItems = useMemo(() => menuitems?.map((name) => <button key={name}>{name}</button>), [menuitems]);
Note that it's now an object (similar to your JSX), and you should not call it, just put it as part of your JSX like this:
return (
<div className="App">
{renderMenuItems}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
I came across your question and it seemed interesting so I researched about it and finally, I found out that NEVER CREATE A COMPONENT INSIDE ANOTHER FUNCTION COMPONENT.
And I found an article written by Kuldeep Bora.
you can go through the article to understand this completely.
https://dev.to/borasvm/react-create-component-inside-a-component-456b
React components automatically re-render whenever there is a change in their state or props.
Function renderMenuItems will re-create on every re-render and it is not an issue.
But if you don't want this behavior you can use the useCallback hook, and then the function will re-create only when one of the dependencies will change.
useCallback hook docs: https://reactjs.org/docs/hooks-reference.html#usecallback
import React, {useState, useEffect} from 'react'
function App() {
const [menuitems, setMenuitems] = useState(null)
useEffect(() => {
console.log("Init")
setMenuitems(["menu1","menu2","menu3"])
},[])
// this function will re-create for every re-render
const renderMenuItems = () => {
if (menuitems && menuitems.length){
return menuitems.map((name) => {
return (
<button key={name}>{name}</button>
)
})
}
}
const [searchTi, setSearchTic] = useState('')
return (
<div className="App">
{renderMenuItems()}
<p>Value: {searchTi}</p>
<input value={searchTi} onChange={(e) => setSearchTic(e.target.value)}/>
</div>
);
}
export default App;
Related
I am try to add search feature to an existing lists of robot names.
In order to do so I am trying to useState hooks. I have an App component and Header component which has the input tag for search field.
Error I am getting is 'InputEvent' is assigned a value but never used.
Below is the code for App component (main component).
import "./App.css";
import Header from "./Header";
import Robo from "./Robo";
import { robots } from "./robots";
import { useState } from "react";
function App() {
const [query, setQuery] = useState("");
const InputEvent = (e) => {
const data = e.target.value;
setQuery(data);
const extraction = robots
.filter((curElem, index) =>
robots[index].name.toLowerCase().includes(query)
)
.map((curElem, index) => {
return (
<Robo
key={robots[index].id}
id={robots[index].id}
name={robots[index].name}
email={robots[index].email}
/>
);
});
return (
<div className="App">
<Header query={query} InputEvent={InputEvent} />
<div className="robo-friends-container">{extraction};</div>
</div>
);
};
}
export default App;
Child component
import React from "react";
import "./header.css";
const Header = ({ query, InputEvent }) => {
return (
<>
<div className="headerText">ROBO FRIENDS</div>
<div>
<input
type="text"
id="lname"
name="lname"
placeholder="Search"
value={query}
onChange={InputEvent}
/>
</div>
</>
);
};
export default Header;
Here is my answer in stackblitz app
https://stackblitz.com/edit/stackoverflow-robots-filter?file=App.tsx,Robo.tsx,Header.tsx,robots.ts
I have altered the code a bit.. you can fork the project and play with it..
You can add debounce option to your input, which prevents unwanted re-renders
Adding the changes:
function App() {
const [query, setQuery] = useState(undefined);
const [filteredRobots, setFilteredRobots] = useState([]);
useEffect(() => {
console.log(query);
const filteredRobots = robots.filter((robot) => {
return robot.name.includes(query);
});
if (filteredRobots.length) {
setFilteredRobots(filteredRobots);
}
}, [query]);
const onQueryChange = (e) => {
const data = e.target.value;
setQuery(data);
};
const renderRobots = () => {
if (!query || !query.length) {
return <p>{'Search to find Robots'}</p>;
}
if (filteredRobots && filteredRobots.length && query && query.length) {
return filteredRobots.map((filteredRobot) => (
<Robo
key={filteredRobot.id} //id is unique key in your data
name={filteredRobot.name}
id={filteredRobot.id}
email={filteredRobot.email}
/>
));
}
return <p>{'No Robots Found'}</p>;
};
return (
<div className="App">
<Header query={query} InputEvent={onQueryChange} />
{renderRobots()}
</div>
);
}
Problems in your code:
Const InputChange is a function that can be used as prop for any React component .. but you have added InputChange inside the InputChange named function itself which is incorrect
Extraction is a jsx variable which is created from Array.filter.. on each item, filter passes a item[index] to the filter function.. you dont want to do robots[index].name.toLowerCase().includes(query).. instead you could have done curElem.name.toLowerCase().includes(query) and same applies for Array.map
When I click the increment button I expect the value displayed to go up but it stays the same
`
import * as React from 'react';
const MyComponent = () => {
var count = 0;
return (
<div>
<h1>Hello World</h1>
<button onClick={() => count++}>{count}</button>
</div>
);
};
`
You're not going to force a re render so your updated variable won't show.
import React, {useState} from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
return (
<div>
<h1>Hello World</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
};
In React you have to use state for rendering or updating.
This is example of increasing and decreasing counter with useState hook.
import React, { useState } from 'react';
export function App(props) {
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount(count + 1);
};
const handleDecrease = () => {
setCount(count === 0 ? 0 : count - 1);
};
return (
<div className='App'>
<div>
<h1>Hello World</h1>
<button onClick={handleIncrease}>Increase</button>
<button onClick={handleDecrease}>Decrease</button>
<h3>Count is: {count}</h3>
</div>
</div>
);
}
You need to use useState hook so the component knows when to rerender and display new data.
I also recommend using useCallback hook to create memoized functions so you prevent unnecessary rerenders of your component (I know in this example it's an overkill but it's still good to know that).
You shouldn't, if possible, return arrow functions on your handlers like onClick - this will also cause the your component to not create new instances of your functions on each render, hence better performance (again not really relevant in this super simple case but a good thing to know nevertheless).
Here are some docs that you can read, these are a really good place to get started with React.
Here's also the code:
const MyComponent = () => {
const [count, setCount] = useState(0);
const handleCountIncrease = useCallback(() => {
setCount((c) => c + 1);
}, [setCount]);
return (
<div>
<h1>Hello World</h1>
<button onClick={handleCountIncrease}>{count}</button>
</div>
)
}
I see here you are using Functional component, so have you tried using React hooks? useState() for example:
import * as React from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
return (
<div>
<h1>Hello World</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div> ); };
Try this maybe?
I am trying to call useEffect funtion onchange of local variable, but its not working is only works if i use it with useState variable, I know there might be some basic thing here that I am not aware of.
sandbox link: https://codesandbox.io/s/affectionate-gareth-igyv7?file=/src/demo.js
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function Demo() {
const [value, setValue] = useState("");
let valueOne, valueTwo;
const setValueOne = (value) => {
valueOne = value;
};
useEffect(() => {
console.log(value);
console.log(valueOne);
}, [value, valueOne]);
return (
<div>
<h1>Demo</h1>
<input
placeholder="useState"
onChange={(e) => setValue(e.target.value)}
/>
<input
placeholder="function"
onChange={(e) => setValueOne(e.target.value)}
/>
{/* {console.log(valueOne)} */}
</div>
);
}
setValueOne will not rerender your component, If you want to fire a re-render, useEffect function needs to have a useState which basically hold state between re-renders.
You can try managing your state like below, its more readable and it will work too.
import React, { useState } from "react";
import "./styles.css";
export default function Demo() {
const [valueOne, setValueOne] = useState("");
const [valueTwo, setValueTwo] = useState("");
const handleValueOne = (e) => {
setValueOne(e.target.value);
};
const handleValueTwo = (e) => {
setValueTwo(e.target.value);
};
return (
<div>
<h1>Demo</h1>
<input
value={valueOne}
placeholder="useState"
onChange={handleValueOne}
/>
<input
value={valueTwo}
placeholder="function"
onChange={handleValueTwo}
/>
{/* {console.log(valueOne)} */}
</div>
);
}
[React] What is the "way" to send/share a function between components?
Better explained in (useless) code
Here I have no problem since everything is in the same component (https://codesandbox.io/s/compassionate-ishizaka-uzlik)
import React, { useState } from "react";
export default function App() {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
const Card = () => <div onClick={letbook}>hey</div>;
const MyCom = () => {
return <div><Card /></div>;
};
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom />
<div>{bookmarks}</div>
</div>
);
}
But then, if now I want to split code, how do I do this? The problem is how to share letbook (this code doesn't work)
import React, { useState } from "react";
export default function App() {
const Card = () => <div onClick={letbook}>hey</div>;
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom />
<div>{bookmarks}</div>
</div>
);
}
const MyCom = () => {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return (
<div>
<Card />
</div>
);
};
I could use a hook that returned the component and the function
const [letbook, MyCom] = useMyCom
But this is not recommended (https://www.reddit.com/r/reactjs/comments/9yq1l8/how_do_you_feel_about_a_hook_returning_components/)
Then I can use a hook and a component, as with the following code, but the code itself seems obfuscated to me, to a point that I doubt whether I should split the code or not
Unless (and this is the question) whether there is a smarter way to do this
import React, { useState } from "react";
export default function App() {
const [bookmarks, setBookmarks, letbook] = useMyCom();
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom card={props => <Card letbook={letbook} />} />
<div>{bookmarks}</div>
</div>
);
}
const Card = ({letbook}) => <div onClick={letbook}>hey</div>;
const useMyCom = () => {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return [bookmarks, setBookmarks, letbook];
};
const MyCom = ({ letbook, card }) => <div>{card(letbook)}</div>;
Split your component to reuse it is definitely a good idea. But make sure your are using and manipulate a single state in the same file an pass it as props. Also, it is important that you avoid to re-render your child component. Only when your main component change props that are necessary to re-render your child component.
import React, { useState, memo } from "react";
const MyCom = memo(props => {
return <div>{props.bookmarks}</div>;
});
export default function App() {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom bookmarks={bookmarks} />
</div>
);
}
I'm playing with hooks, and I'm trying to do the following:
import React, { useState, useRef } from 'react';
const EditableField = () => {
const [isEditing, setEditing] = useState(false);
const inputRef = useRef();
const toggleEditing = () => {
setEditing(!isEditing);
if (isEditing) {
inputRef.current.focus();
}
};
return (
<>
{isExpanded && <input ref={inputRef} />}
<button onClick={toggleEditing}>Edit</button>
</>
);
};
This is going to fail, because current is null, since the component haven't re-rendered yet, and the input field is not yet rendered (and therefore can't be focused yet).
What is the right way to do this? I can use the usePrevious hook proposed in the React Hooks FAQ, but it seems like a painful workaround.
Is there a different way?
You can use the useEffect hook to run a function after every render when isEditing changed. In this function you can check if isEditing is true and focus the input.
Example
const { useState, useRef, useEffect } = React;
const EditableField = () => {
const [isEditing, setEditing] = useState(false);
const toggleEditing = () => {
setEditing(!isEditing);
};
const inputRef = useRef(null);
useEffect(() => {
if (isEditing) {
inputRef.current.focus();
}
}, [isEditing]);
return (
<div>
{isEditing && <input ref={inputRef} />}
<button onClick={toggleEditing}>Edit</button>
</div>
);
};
ReactDOM.render(<EditableField />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I know the accepted answer covers the requested element in the above question.
But as an additional note, if you are using functional components, make use of React.forwardRef to pass down the reference to child components. It might be
definitely useful for someone who refers to this question later on.
In a more cleaner way, you can write your child component which accept the ref as given below:
const InputField = React.forwardRef((props, ref) => {
return (
<div className={props.wrapperClassName}>
<input
type={props.type}
placeholder={props.placeholder}
className={props.className}
name={props.name}
id={props.id}
ref={ref}/>
</div>
)
})
Or Simply use this component
import { FC, useEffect, useRef } from 'react'
export const FocusedInput: FC<JSX.IntrinsicElements['input']> = (props) => {
const inputRef = useRef<null | HTMLElement>(null)
useEffect(() => {
inputRef.current!.focus()
}, [])
return <input {...props} type="text" ref={inputRef as any} />
}