I'm trying to reuse the variable msg in another component which is presumably scoped to the component it's declared in. The problem is the variable uses state and cannot be declared above the scope to be accessible to both components.
The App component that tries to use the component shows this error: msg is not defined
What's the best workaround to solve this issue?
import React, { useState } from "react";
const Test = () => {
const [msg, setMsg] = useState(null);
return (
<>
<p>state value from component: {msg}</p>
<button onClick={() => setMsg("changed")}>change state</button>
</>
);
};
const App = () => {
return (
<div>
{msg && ( // error here
<p>show this</p>
)}
<p>some text</p>
<Test />
</div>
);
};
export default App;
Here's a stackblitz for convenience: https://stackblitz.com/edit/react-g16stb
Thanks for any help here
you can't pass variables to parent or siblings you need to lift up state. find a common ancestor to place your state and pass down as props.
your code should look like:
const Test = ({ msg,setMsg }) => {
return (
<>
<p>state value from component: {msg}</p>
<button onClick={() => setMsg("changed")}>change state</button>
</>
);
};
const App = () => {
const [msg, setMsg] = useState(null);
return (
<div>
{msg && (
<p>show this</p>
)}
<p>some text</p>
<Test msg={msg} setMsg={setMsg} />
</div>
);
};
export default App;
In-order to access msg in the App component it is difficult to access in the parent component from the above code, So we need to lift up the state and place msg in the App component and pass it as a prop to the Test component
import React, { useState } from "react";
const Test = ({ msg,setMsg }) => {
return (
<>
<p>state value from component: {msg}</p>
<button onClick={() => setMsg("changed")}>change state</button>
</>
);
};
const App = () => {
const [msg, setMsg] = useState(null);
return (
<div>
{msg && (
<p>show this</p>
)}
<p>some text</p>
<Test msg={msg} setMsg={setMsg} />
</div>
);
};
export default App;
Related
I would like to pass several data in a modal. My modal works fine but does not display the data I want. I would like to display the data BY ID, but I get all the data. This is my code :
App.js :
import React, { useState, useEffect } from "react";
import Modal from "./Modal";
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const App = (props) => {
const [image, setImage] = useState([])
return (
<div>
{image.map((image) => {
return (
<div key={image.id}>
<img src={...}
alt={image.title}
/>
<ExpandMoreIcon onClick={handleClick} />
</div>
)
})}
</div>
);
}
export default App;
Modal.js :
import React from 'react';
const Modal = ({ showModal, image }) => {
console.log("result", image);
return (
<div className="modal" >
<div className='modal__content'>
<h1 className='modal__title'>{image.title}</h1>
<p className='modal__description'>{image.description}</h1>
</div>
</div>
);
}
export default Modal;
I think the problem comes from here image={image} in App.js because I get 8 tables in console.log("result", movie);
{showModal && <Modal showModal={handleClick} image={image} />}
Your problem is your modal state is true/false value, so that's why once you trigger showModal, it will show all modals according to your number of images
The fix can be
Note that I modified your state name to align with new state values
const [modalImage, setModalImage] = useState(); //undefined as no image selected
const handleClick = (imageId) => {
setModalImage(imageId);
};
Your elements will be like below
<ExpandMoreIcon onClick={() => handleClick(image.id)} />
{modalImage === image.id && <Modal showModal={() => handleClick(image.id)} image={image} />}
If you want to close the modal, you can reset modalImage state
onClick={() => handleClick(undefined)}
I just noticed that you also can move your modal out of the image loop, and potentially you can pass the entire image object to the modal too
The full implementation
import React, { useState, useEffect } from "react";
import Modal from "./Modal";
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const App = (props) => {
const [image, setImage] = useState([])
const [modalImage, setModalImage] = useState(); //no image selected
const handleClick = (image) => {
setModalImage(image);
};
useEffect(() => {
...
}, []);
return (
<div>
{image.map((image) => {
return (
<div key={image.id}>
<img src={...}
alt={image.title}
/>
<ExpandMoreIcon onClick={() => handleClick(image)} />
</div>
)
})}
{modalImage && <Modal showModal={() => handleClick(modalImage)} image={modalImage} />}
</div>
);
}
export default App;
Both the map item and the image state array are named image in your code here;
image.map((image) => {
/// your code
})
which could be the ambiguity that led to showing the whole array of data instead of one image in the array.
I am generating my state in the parent component. latestFeed generates a series of posts from my backend:
import React, { useState, useEffect } from "react";
import { getLatestFeed } from "../services/axios";
import Childfrom "./Child";
const Parent= () => {
const [latestFeed, setLatestFeed] = useState("loading");
const [showComment, setShowComment] = useState(false);
useEffect(async () => {
const newLatestFeed = await getLatestFeed(page);
setLatestFeed(newLatestFeed);
}, []);
return (
<div className="dashboardWrapper">
<Child posts={latestFeed} showComment={showComment} handleComment={handleComment} />
</div>
);
};
export default Parent;
then latestFeed gets generated into a series of components that all need to hold their own state.
import React, { useState } from "react";
const RenderText = (post, showComment, handleComment) => {
return (
<div key={post._id} className="postWrapper">
<p>{post.title}</p>
<p>{post.body}</p>
<Comments id={post._id} showComment={showComment} handleComment={() => handleComment(post)} />
</div>
);
};
const Child= ({ posts, showComment, handleComment }) => {
return (
<div>
{posts.map((post) => {
return RenderText(post, showComment, handleComment);
})}
</div>
);
};
export default Child;
In its current form, the state of RenderText's is all set at the same time. I need each child of Child to hold its own state.
Thank you!
Instead of using RenderText as a function, call it as a component:
{posts.map((post) => (
<RenderText key={post.id} post={post} showComment={showComment} />
))}
This is because when used as a component, it will have it's own lifecycle and state. If used as a function call, React does not instantiate it the same way - no lifecycle, no state, no hooks, etc.
Issue:
within my child component:
TypeError: items.map is not a function
I don't understand why not, I saw that others had this issue when they were mapping through something that wasn't an array. However mine clearly is. As seen on the fourth line in the parent component.
Parent comp:
import React, {useState} from 'react';
import ListItem from '../components/listItem';
const Pagethree = () => {
const[items,setItem] = useState([]);
return(
<div>
<CreateItem items={items} setItem={setItem}/>
<ListItem items={items}/>
</div>
)
};
export default Pagethree;
Child comp 1:
import React, {useState} from "react";
const ListItem = (props) =>{
const {items} = props;
return(
<div>
<h1>list item comp</h1>
<ul>
{
items.map((item, i) =>
<div class="flex">
<input type="checkbox" />
{item}
<button>delete</button>
</div>
)
}
</ul>
</div>
)
};
export default ListItem;
child component 2:
import React from "react";
const CreateItem = (props) =>{
const {items, setItem} = props;
const addItem = (e) =>{
e.preventDefault();
setItem([...items, items]);
setItem("");
console.log(items);
}
return(
<div>
<h1>create item comp</h1>
<form onsubmit={addItem}>
<input type="text" value={items} onChange={e => setItem(e.target.value)}/>
<button type="submit">add item</button>
</form>
</div>
)
};
export default CreateItem;
It's not entirely clear what your goal is, but this may help. It's a basic form that adds individual items (on submit) to an items list.
const { useState } = React;
const App = () => {
const [items, setItems] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
const item = e.target.elements['itemInput'].value;
setItems([...items, item]);
e.target.reset();
}
const itemsDisplay = `Your items are: ${items.join(', ')}`;
return (
<React.Fragment>
<p>{itemsDisplay}</p>
<form onSubmit={handleSubmit}>
<input name='itemInput' />
<button type='submit'>Add Item</button>
</form>
</React.Fragment>
);
}
ReactDOM.render(<App />, document.body);
<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>
When you call setItem on input change (in CreateItem) you’re setting the state on the parent component to a single (non-array) value. The subsequent render throws because items is no longer an array.
I am not very experienced with React but I have a very simple Setup.
export default function App() {
const [title, setTitle] = useState("still-empty");
const myFunction = title => {
setTitle(title);
};
return (
<div className="App">
<ComponentA myFunction={myFunction} />
<br />
<br />
<ComponentB title={title} />
</div>
);
}
const ComponentA = ({ myFunction }) => {
console.log("Rendering Component A");
return (
<div onClick={() => myFunction(Math.random() * 1000)}> Component A </div>
);
};
export default ComponentA;
const ComponentB = ({ title }) => {
return <div> Title : {title}</div>;
};
export default ComponentB;
Here is a sandbox to test this: https://codesandbox.io/s/musing-cookies-g7szr
See that if you click on "ComponentA", that exact ComponentA gets rerendered (you can see it in console) although no props are changed on this component. This is a simplified example of my real use case. In my real use case, ComponentA is a map where a lot of stuff (zoom, center)
will be reset. I want to prevent these resets and also the 1 second it takes for rerendering. Therefor I present this simplified example.
So how do I pass an information from ComponentA to ComponentB, without rerendering ComponentA itself? Thanks for helping out here.
use useCallback in Parent so that the function is not created again and again but only on initial render.
use React.memo so that when no props are changed the component wont re-render.
App
export default function App() {
const [title, setTitle] = useState("still-empty");
const myFunction = useCallback(title => {
setTitle(title);
}, []);
return (
<div className="App">
<ComponentA myFunction={myFunction} />
<br />
<br />
<ComponentB title={title} />
</div>
);
}
ComponentA
import React, { memo } from "react";
const ComponentA = ({ myFunction }) => {
console.log("Rendering Component A");
return (
<div onClick={() => myFunction(Math.random() * 1000)}> Component A </div>
);
};
export default memo(ComponentA);
Working demo is here:
https://codesandbox.io/s/affectionate-boyd-v7g2t?file=/src/App.js
I have the main component as below.
const MainApp: React.FC = () => {
return (
<div>
<DummyComp/>
<ComponentA />
<ComponentB />
</div>
);
}
export default App;
"Component B" has some form elements and when it's value changes, I require all the form values to be passed to the "Component A".
All the examples that I found are not using Functional Components. So I am not sure how to create callback functions and use states in Functional Components.
const MainApp: React.FC = () => {
const [formData, setFormData ] = useState({});
return (
<div>
<DummyComp/>
<ComponentA formData={formData} />
<ComponentB onClick={setFormData} />
</div>
);
}
export default App;
You can edit formData object as you wish in ComponentB with setFormData method, and you can read the formData object in ComponentA.
You can either use Redux (Global State) to do this, OR use a state manage in the Main Component.
const MainApp: React.FC = () => {
const [data, setData] = useState();
useEffect(() => {
setData() //anyhting initial
},[formdata]);
const handleData = (data) => {
setData(data)
}
return (
<div>
<DummyComp/>
<ComponentA data={data} />
<ComponentB onformclick={handleData} />
</div>
);
}
export default App;
Lets Suppose component A is like
const ComponentA: React.FC = ({props}) => {
const data = props.data;
return (
<div>
//any html here
//example
<div>
{
data ?
data.property //any property existing on data
:null
}
</div>
</div>
);
}
export default ComponentB;
Lets Suppose component A is like
const ComponentA: React.FC = ({props}) => {
const [formdata, SetformData] = useState();
return (
<div>
//form here
//any button or any element to submit form
<button onClick={()=>props.onformclick(formdata)}> submit </button>
</div>
);
}
export default ComponentB;