there is a react component stored inside of a state variable. I've attached a sample code and created a codesandbox sample. As you can see, nameBadgeComponent is supposed to display {name} field from state. It works fine for the default value, but does not react to changes in name variable. Is there a way to make it work without updating the nameBadgeComponent itself?
const [name, setName] = useState("DefaultName");
const [nameBadgeComponent] = useState(<h2>My name is: {name}</h2>);
return (
<div className="App">
{nameBadgeComponent}
<button
onClick={() => {
const newName = Math.random()
.toString(36)
.substring(3);
console.log("Renamed to:", newName);
setName(newName);
}}
>
rename
</button>
</div>
);
You can't create a component from a useState. A state is all the properties and data that define in what condition/appearance/etc the component is. A useState allows to control a piece of information or data, but doesn't respond to changes, a component does.
The fact that you put "Component" in the name should give you a hint ;)
What you want to do is probably this :
function NameBadgeComponent({ name }) {
return <h2>My name is: {name}</h2>;
}
export default function App() {
const [name, setName] = useState("DefaultName");
return (
<div className="App">
<NameBadgeComponent name={name} />
<button
onClick={() => {
const newName = Math.random()
.toString(36)
.substring(3);
console.log("Renamed to:", newName);
setName(newName);
}}
>
rename
</button>
</div>
);
}
This will update to the changes in name properly.
Why are you extracting the nameBadgeComponent to a state? That is useless as it only relies on name, keeping your code as close to the original as possible:
const [name, setName] = useState("DefaultName");
const nameBadgeComponent = <h2>My name is: {name}</h2>;
return (
<div className="App">
{nameBadgeComponent}
<button
onClick={() => {
const newName = Math.random()
.toString(36)
.substring(3);
console.log("Renamed to:", newName);
setName(newName);
}}
>
rename
</button>
</div>
);
}
Context as to why it does not work: if you use useState you are defining the starting state, the code in there is never run again unless you use setState (in this case setNameBadgeComponent)
Therefore its never updated
EDIT:
The other answer defining the extra component is more reactish code, if you are going to accept an answer accept that one.
Related
I have an issue with my code below. When you click the add code button, it adds the code to the monaco code editor which is great. However, if you type some more code in the editor or erase whats currently there and then press the 'Add code' button, nothing is added. It's just blank.
Is there a way to whenever that 'Add code' button is clicked it clears everything in the editor and just adds the setAddCode state when the button is clicked?
And here is the code:
import { useState, useRef, useEffect } from "react";
import Editor from "#monaco-editor/react";
export default function IndexPage() {
const [input, setInput] = useState("");
const [addCode, setAddCode] = useState("# code goes here");
const editorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
setInput((editorRef.current = editor));
editorRef.current = editor;
}
return (
<div>
<div className="code-editor">
<Editor
width="100vh"
height="40vh"
theme="vs-dark"
fontSize="14"
defaultLanguage="python"
defaultValue=""
value={addCode}
onMount={handleEditorDidMount}
/>
</div>
<br />
<button onClick={() => setAddCode("print('Hello World!')")}>
Add code
</button>
</div>
);
}
The way the Editor component is set up, it will only change the value in the editor if you pass a different value prop. It's probably doing something similar to the following:
const Editor = ({ value }) => {
const [codeToDisplay, setCodeToDisplay] = useState(value);
useEffect(() => {
setCodeToDisplay(value);
}, [value]);
// etc
In your parent component, when you call setAddCode("print('Hello World!')") the first time, that'll result in the child component seeing a difference in how it was called - the value prop changed - so it'll know that there's something different to display, and update its own internal state appropriately. When you press the button again, the addCode value will stay the same - the Editor component doesn't see any difference, so it won't update.
To fix it, you can listen for changes from inside Editor by using its onchange prop to update the state in the parent component when the code inside the editor changes - that way, when you click the button later, the prop will be different, and the editor will know to update its internal state.
export default function IndexPage() {
const [input, setInput] = useState("");
const [codeValue, setCodeValue] = useState("# code goes here");
const editorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
setInput((editorRef.current = editor));
editorRef.current = editor;
}
return (
<div>
<div className="code-editor">
<Editor
width="100vh"
height="40vh"
theme="vs-dark"
fontSize="14"
defaultLanguage="python"
defaultValue=""
value={codeValue}
onChange={(newValue) => { setCodeValue(newValue); }}
onMount={handleEditorDidMount}
/>
</div>
<br />
<button onClick={() => setCodeValue("print('Hello World!')")}>
Add code
</button>
</div>
);
}
className "page" needs to be modified to "page light-theme" or "page dark-theme" on toggle through local storage key "theme-color" with values of light-theme and dark-theme.
The active key value does change in local Storage but updates only show if the pages is refreshed. I need the changes to sync on toggle
Page to be changed
export default function Page({children}){
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
{children}
</div>
)
}
h3 inner text needs to change dynamically depending on the active value from key "theme-color" in local storage. I have place the variable "themeOpener" in between h3 tag. No changes take place
heres is my code
export default function Body() {
let themeOpener;
if (`${localStorage.getItem('theme-color','light-theme')}`) {
themeOpener = "🧛🏼Ahh the light it burns! Please use toggle, I prefer dark mode!";
} else {
themeOpener = "I learnt to design in React and im hooked 🤩";
}
return (
<div className="body">
{/* <h3 id="opener">I learnt to design in React and im hooked 🤩</h3> */}
<h3 id="opener">{themeOpener}</h3>
</div>
);
}
This is code for my toggle where local storage is created
const ToggleMode = () => {
// state
const [isLight, setIsLight] = useState(false);
// effect
useEffect(() => {
// check local storage
const CurrentTheme = localStorage.getItem("theme-color");
if (CurrentTheme === "light-theme") {
setIsLight(true);
} else {
setIsLight(false);
}
console.log(useEffect);
}, []);
const ToggleChecked = () => {
// logic
if (isLight) {
localStorage.setItem("theme-color", "dark-theme");
setIsLight(false);
} else {
localStorage.setItem("theme-color", "light-theme");
setIsLight(true);
}
console.log(ToggleChecked);
};
return (
<div className="toggle--container">
<input
type={"checkbox"}
id="toggle"
className="toggle--checkbox"
checked={isLight}
onChange={ToggleChecked}
/>
<label htmlFor="toggle" className="toggle--label">
<span className="toggle--label-background"></span>
</label>
<div className=""></div>
</div>
);
};
export default ToggleMode;
Yes, this is natural according to your code. Whenever toggling, ToggleMode component will only be re-rendered by changed state value isLight.
But your Body and Page component which are supposed to be ToggleMode's parent will not be re-rendered. Because their props or states never changed by ToggleChecked().
To get it done working, you need to do something to re-render parents in Page and Body component.
How? You need to create a state value in those components or create IsLight and setIsLight at the top level component. And then these two would be drilled into ToggleMode.
Something like followings.
export default function Page({children}){
// state
const [isLight, setIsLight] = useState(false);
return(
<div className={`page ${localStorage.getItem('theme-color')}`}>
{/* <div className= "page"> */}
<Body isLight={isLight} setIsLight={setIsLight} />
{children}
</div>
)
}
export default function Body({isLight, setIsLight}) {
return (
<div>
<ToggleMode isLight={isLight} setIsLight={setIsLight} />
</div>
);
}
export default function ToggleMode ({isLight, setIsLight}){
// This is not needed anymore.
// const [isLight, setIsLight] = useState(false);
return (<>Your toggle code...</>)
}
P.S. Don't you think this is quite irritating? To avoid prop drilling, we use state management utilities such as react context API or 3rd party libraries such as Redux.
Currently I am facing the problem that I want to change a state of a child component in React as soon as a prop is initialized or changed with a certain value. If I solve this with a simple if-query, then of course I get an infinite loop, since the components are then rendered over and over again.
Component (parent):
function App() {
const [activeSlide, setActiveSlide] = useState(0);
function changeSlide(index) {
setActiveSlide(index);
}
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
Component (child):
function Button(props) {
const Icon = Icons[props.icon];
const [activeClass, setActiveClass] = useState("");
// This attempts an endless loop
if(props.active == props.index) {
setActiveClass("active");
}
function toggleView(e) {
e.preventDefault();
props.handler(props.index);
}
return(
<button className={activeClass} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
}
Is there a sensible and simple approach here? My idea would be to write the if-query into the return() and thus generate two different outputs, even though I would actually like to avoid this
The React docs have a nice checklist here used to determine if something does or does not belong in state. Here is the list:
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.
The active class does not meet that criteria and should instead be computed when needed instead of put in state.
return(
<button className={props.active == props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
This is a great use of useEffect.
instead of the if statement you can replace that with;
const {active, index} = props
useEffect(_ => {
if(active == index) {
setActiveClass("active");
}
}, [active])
The last item in the function is a dependency, so useEffect will only run if the active prop has changed.
React automatically re-renders a component when there is a change in the state or props. If you're just using activeClass to manage the className, you can move the condition in the className as like this and get rid of the state.
<button className={props.active === props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
however, if you still want to use state in the child component, you can use the useEffect hook to to update the state in the child component.
Try to use the hook useEffect to prevent the infinite loop. (https://fr.reactjs.org/docs/hooks-effect.html)
Or useCallback hook. (https://fr.reactjs.org/docs/hooks-reference.html#usecallback)
Try this and tell me if it's right for you :
function App() {
const [activeSlide, setActiveSlide] = useState(0);
const changeSlide = useCallback(() => {
setActiveSlide(index);
}, [index]);
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
I have a component with an input text field (called SingleItem in the code below). For storing and updating the value of this text field I am using the useState hook.
Now, the parent component can have multiple instances of SingleItem and also re-order them.
The problem is that, after they are reordered, it seems that their states are staying in the same order. My understanding is that the hooks rely on the call order so that can explain it.
My question is, what is the proper way of implementing this scenario. I'm thinking that ReorderComponents can hold the state for all the SingleItems but then passing lambdas for updating can become messy.
function SingleItem(props: {item: string}) {
const [value, setValue] = useState<string>(props.item);
return <div>
<TextField value={value} onChange={(e) => setValue(e.target.value)}/>
<span>Input arg: {props.item}</span>
</div>;
}
function ReorderComponents() {
const [items, setItems] = React.useState(["a", "b", "c"]);
return (
<div className="App">
{items.map(number => (
<SingleItem item={number}/>
))}
<Button variant={'contained'} onClick={() => setItems([...items.reverse()])}>Reverse</Button>
</div>
);
}
This is what happens after the button is clicked.
Jayce444 answered in the comments. I was missing the key property in SingleItem
<SingleItem item={number} key={number} />
I am trying to create my first search bar in React.js. I am trying to implement search functionality with filter method. I faced a problem with filter method, which gives an error like "filter is not defined". I am stuck on it for 2 days, I have looked several tutorials and endless youtube videos. This is the simpliest approach, I guess. Any help will be appreciated.
import React, { useState, useEffect } from "react";
import Recipe from "./Recipe";
import "./styles.css";
export default function RecipeList() {
const apiURL = "https://www.themealdb.com/api/json/v1/1/search.php?f=c";
const [myRecipes, setRecipes] = useState("");
const [search, setSearch] = useState("");
// fetch recipe from API
function fetchRecipes() {
fetch(apiURL)
.then(response => response.json())
.then(data => setRecipes(data.meals))
.catch(console.log("Error"));
}
function onDeleteHandler(index) {
setRecipes(
myRecipes.filter((element, filterIndex) => index !== filterIndex)
);
}
useEffect(() => {
fetchRecipes();
}, []);
const filterRecipes = myRecipe.meal.filter( element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase())
})
{/* filter method above doesn't work */}
return (
<div>
<label>
<div className="input-group mb-3 cb-search">
<input
type="text"
className="form-control"
placeholder="Search for recipes..."
aria-label="Recipient's username"
aria-describedby="button-addon2"
onChange = {e => setSearch (e.target.value)}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
id="button-addon2"
>
Search
</button>
</div>
</div>
</label>
<div>
<button
className="btn btn-info cb-button fetch-button"
onClick={fetchRecipes}
>
Fetch Recipe
</button>
<br />
{filterRecipes.map((element, index) => (
<Recipe
key={index}
index = {index}
onDelete={onDeleteHandler}
{...element}
name = {element.strMeal}
/>
))}
{/** name of child component */}
{/** strMeal is the name of Recipe in API object */}
</div>
</div>
);
}
link for code codesandbox
I made some changes on your code updated code
const [myRecipes, setRecipes] = useState([]);
You should declare myRecipes as an array if u intended to use map function.
const filterRecipes = myRecipe.meal.filter( element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase())
})
You have the wrong variable passing through, it should be myRecipes
filterRecipes.map((element, index) => (
<Recipe
key={index}
index = {index}
onDelete={onDeleteHandler}
{...element}
name = {element.strMeal}
/>
3. You should check whether your filterRecipes is not undefined before you use map function.
Lastly, your fetch API return error which unable to setRecipes.
I could not resolve you task completely because of low count of information according the task, but, i think, my answer will be useful for you.
So, tthe first thing I would like to draw attention to is a initial state in the parameter of useState function. In this task it sould be as:
const [myRecipes, setRecipes] = useState({meals: []});
Because, before fetching data, React has a time to run the code, and, when it come to line 32, it see what in the myRecipes (myRecipes, not a myRecipe. Please, pay attention when you write the code) a string except an array.
And in the line 32 i recommend you to add something checking of have you resolved request of data like:
const filterRecipes = myRecipes.meals.length
? myRecipes.meals.filter(element => {
return element.name.toLowerCase().includes(search.toLocaleLowerCase());
});
: []
And look in the data which you receive, because, i think, there are no elements with propName like name (element.name).
I think, i could help you as possible. If you have any questions, ask in comments. Will answer you as soon as possible. Good luck