Update data in parent when added in child in react - javascript

I'm new to React. I see a lot of posts for updating children when the parent is updated but I haven't found the opposite.
I have a parent component (MainComponent) fetching and storing data. The form to add data (FormNewSubItem ) is in a child component.
When I submit the form I add a subitem in an item and I want the array of items in main component to store the new data.
In MainComponent.jsx
storeItems(items) {
// store items in db
}
retrieveItems() {
// return items from db
}
render {
return (
<MainComponent>
<ListItems items={this.retrieveItems()} />
</MainComponent>
)
}
In ListItems.jsx
render {
return (
<div>
{this.props.items.map((item, idx) => <Item item={item} key={idx} />)}
</div>
)
}
In Item.jsx
handleNewSubItem = (subItem) => {
this.props.item.addSubItem(subItem);
// how to update the 'fetchedItems' in MainComponent ?
}
render {
return (
<React.Fragment>
<div className="title">{this.props.item.title}</div>
<div className="content">
<ListSubItems subitems={this.props.item.subitems}>
<FormNewSubItem handleNewSubItem={thid.handleNewSubItem} />
</ListSubItems>
</div>
</React.Fragment>
)
}

So there are a few ways you can do this. A common way would to connect both components to the same set of data using a library like Redux.
But if you just want to do this in React, then you will need to define the function in the top level component and then pass it down as props to the child component.
So the MainComponent.js will have a function like
addSubItem = (item) => {
// update the state here
}
Then you will pass it down as props to the component you want to use it in, so pass it to the ListItems.js component by
<ListItems items={this.retieveItems()} addSubItem={addSubItem} />
and then again to the Item.js component
<Item item={item} key={idx} addSubItem={this.props.addSubItem}/>
then in the Item component just call it with this.props.addSubItem
Because it is scoped to the top level component, when you call the function in the child components and pass it an item, it will update the state in the parent component

Look into using Context and the useContext hook if you don’t want to use redux for a simple app.

Related

How to get state from two child components in React

I have a tabbed modal dialog and in one of my tabs I am rendering two image list components. Each component manages an array of image objects based on some props that are defined in the parent. In my dialog I have a single save button and I need to call the backend api to update or delete any image state from either of the image list components. For example
function MyItem() {
function handleSave() {
// How would I get the state from my ImageList components?
}
//other handlers
return (
<TabPanel index={0} title="Detail">
<HeaderWithSaveButton onSaveClick={handleSave} />
<SomeTextContent/>
</TabPanel>
<TabPanel index={1} title="Images">
<ImageList key="banner" />
<ImageList key="icon" />
</TabPanel>
)
}
The ImageList components maintain their own state in an array about the images that are added or removed.
function ImageList({key}) {
const [images, setImages] = useState([{imageKey: key, url:`/images/${key}`, fileData: null}])
function handleImageSelected(image){
setImages() // add image
}
// other handlers
return (
<ImageUploader/>
<SortedImageList images={images} />
)
}
I have the image list working but obviously the state is local to each one, so I have no way to access it in the parent's save button in Item component.
Is this something I can use Context for? Would there have to be two contexts that will be merged? If I lifted the state up to the Item component, how would I keep track of both of those arrays? Also, the item component is getting bloated already.
But the basic question is an approach to manage the state in the two image lists yet access it in the parent so I can figure out what needs to be sent to the api when they save.
You could pass a state update function to each component to allow them to update the state of their parent. I'm not sure if this is a particularly 'React-y' way to do it but something like:
function MyItem() {
const [imageState, setImageState] = useState({});
function handleSave() {
// Use imageState to access the images
}
//other handlers
return (
<TabPanel index={0} title="Detail">
<HeaderWithSaveButton onSaveClick={handleSave} />
<SomeTextContent/>
</TabPanel>
<TabPanel index={1} title="Images">
<ImageList key="banner" setImageState={setImageState} />
<ImageList key="icon" setImageState={setImageState} />
</TabPanel>
)
}
And then your ImageList component can use the passed state setter to inform the parent component:
function ImageList({key, setImageState}) {
const [images, setImages] = useState([{imageKey: key, url:`/images/${key}`, fileData: null}])
function handleImageSelected(image){
setImages() // add image
setImageState((current) => {
return {
...current,
[key]: image,
}
})
}
// other handlers
return (
<ImageUploader/>
<SortedImageList images={images} />
)
}
The other solution is to 'raise' the state to the parent component

How to import a component that is rendered in another component?

I have a form component rendered in a component, component1, and I want to use it in another component, component2, but I want to use it with all the functionality that it has inside component1. How can I do this? I tried exporting it from inside component1 but it didn't work.
Here is where the form component is rendered in component1:
return (
<OutsideClickHandler onOutsideClick={this.handleBlur}>
//some not important code was here
<div
id={id}
className={popupClasses}
ref={node => {
this.filterContent = node;
}}
style={contentStyle}
>
{this.state.isOpen ? (
<FilterForm // this FilterFrom I want to export into another component
id={`${id}.form`}
paddingClasses={popupSizeClasses}
showAsPopup
contentPlacementOffset={contentPlacementOffset}
initialValues={initialValues}
keepDirtyOnReinitialize={keepDirtyOnReinitialize}
onSubmit={this.handleSubmit}
onChange={this.handleChange}
onCancel={this.handleCancel}
onClear={this.handleClear}
>
{children}
</FilterForm>
) : null}
</div>
</div>
</OutsideClickHandler>
);
}
}
Here is where I want to import and use the form component:
return (
<div className={classes}>
//some not important code was here
<ModalInMobile
id="SearchFiltersMobile.filters"
isModalOpenOnMobile={this.state.isFiltersOpenOnMobile}
onClose={this.cancelFilters}
showAsModalMaxWidth={showAsModalMaxWidth}
onManageDisableScrolling={onManageDisableScrolling}
containerClassName={css.modalContainer}
closeButtonMessage={modalCloseButtonMessage}
>
//here I want to import and use the *form component*
</ModalInMobile>
</div>
);
FilterForm job is to perform certain logics and display some elements in the DOM, based on its props and states (Just like how system works, you can have a car parked or have a car running both are the same system but with different inputs and states). So you already have your component. If you mean that attributes like popupSizeClasses, contentPlacementOffset, keepDirtyOnReinitialize, .... are shared between component1 and component2, you can make a wrapper around FilterForm with those values passed to FilterForm. So you would render FilterFormWrapper component instead.
But make sure that some props like onCancel (which is equal to this.handleCancel) are independent and does not have any dependency (state or props) to component1, so you can extract them.

How to pass React Component as props to another component

I am trying to call PopupDialog.tsx inside Content.tsx as a sibling of Item.tsx.
Previously PopupDialog.tsx is called inside C.tsx file but due to z index issue i am trying to bring it out and call it in Content.tsx
Is it possible to somehow pass the whole component(popupDialog and its parameters) in Content.tsx so that i could avoid passing back and forth the parameters needed for popupdialog in content.tsx.
Code in C.tsx where PopupDialog component is called.
const C = (props: Props) => (
<>
{props.additionalInfo ? (
<div className="infoButton">
<PopupDialog // need to take this code out and want to add in Content.tsx
icon="info"
callback={props.callback}
position={Position.Right}
>
<div className="popuplist">{props.additionalInfo}</div>
</PopupDialog>
</div>
) : (
<Button className="iconbutton"/>
)}
</>
);
Content.tsx where i would like to call PopupDialog.tsx with its parameters
const Content = (props: Props) => {
const [componentToRender, docomponentToRender] = React.useState(null);
const [isAnimDone, doAnim] = React.useState(false);
return (
<div className="ContentItems">
<PWheel agent={props.agent} />
{isAnimDone && (
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog/> //want to call here with all its parameters to be passed
}
</>
)}
</div>
);
};
Folder Structure
App.tsx
->ViewPort.tsx
->Content.tsx
->PWheel.tsx
->Item.tsx
->A.tsx
->B.tsx
->C.tsx
{props.additionalinfo &&
->PopupDialog.tsx
->PopupDialog.tsx
So if I understand the question correctly you want to pass one component into another so that you can use the properties or data of the passed componenet in your current component.
So there are three ways to achieve this.
1)Sending the data or entire component as prop.This brings disadvantage that even though components which don't require knowledge
about the passed component will also have to ask as a prop.So this is bascially prop drilling.
2)The other is you can use context api.So context api is a way to maintain global state variale.so if you follow this approach you don't need to pass data or componenet as props.Wherever you need the data you can inport context object and use it in componenet.
3)Using Redux library.This is similar to context api but only disadavantage is that we will have to write lot of code to implement this.Redux is a javascript library.
Let me know if you need more info.
You need to :
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog abc={componentToRender} /> //you must call in this component, in this case i name it is abc , i pass componentToRender state to it
}
</>
and then PopupDialog will receive componentToRender as abc, in PopupDialog , you just need to call props.abc and done .
If you need to know more about prop and component you can see it here
I think what you want to use is Higher-Order-Components (HOC).
The basic usage is:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Below is such an implementation that takes a component (with all its props) as a parameter:
import React, { Component } from "react";
const Content = WrappedComponent => {
return class Content extends Component {
render() {
return (
<>
{/* Your Content component comes here */}
<WrappedComponent {...this.props} />
</>
);
}
};
};
export default Content;
Here is the link for higher-order-components on React docs: https://reactjs.org/docs/higher-order-components.html
Make use of
useContext()
Follow this for details:
React Use Context Hook

How to re-render a component with React-Router <Link> pointing to the same URL

To keep it simple, the detail page fetches data on mount based on the movie ID in the URL, this coming from path='movie/:id' in the Route.
It's child is called Recommended, which shows you recommended movies based again on the current URL.
class MovieDetailPage extends React.Component {
// Fetch movies and cast based on the ID in the url
componentDidMount() {
this.props.getMovieDetails(this.props.match.params.id)
this.props.getMovieCast(this.props.match.params.id)
}
render() {
<div>
Movies here
</div>
<Recommended id={this.props.match.params.id}/>
}
}
The Recommended component fetches data based on the current movie as well and generates another tag pointing to another movie.
class Recommended extends React.Component {
componentDidMount() {
this.props.getRecommended(this.props.id)
}
render() {
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{
this.props.recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
>
</img>
</Link>
)
})
}
</div>
</>
)
}
}
Now how can I trigger another render of the parent component when clicking the Link generated in the Recommended component? The URL is changing but this won't trigger a render like I intent to do.
UPDATE:
<Route
path="/movie/:id"
render={(props) => (
<MovieDetailPage key={props.match.params.id}
{...props}
)}
/>
I passed in a unique key this time that triggered the re-render of the page. I tried this before but I might've screwed up the syntax.
This post got me in the right direction: Force remount component when click on the same react router Link multiple times
Add a key to the page
If you change route but your page is not getting its "mount" data then you should add a key to the page. This will cause your page to rerender and mount with the new id and get the data again.
You can read more about react keys here
A key tells react that this is a particular component, this is why you see them in on lists. By changing the key on your page you tell react that this is a new instantiation of the component and has changed. This will cause a remount.
Class component example
class MyPage extends React.Component {
componentDidMound() {
// this will fire each time the key changes since it triggers a mount
}
render() {
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
}
Functional component example
const MyPage = (props) => {
React.useEffect(() => {
// this will fire each time the key changes
}, []);
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
You can add another React lifecycle method that triggers on receiving new props (UNSAFE_componentWillReceiveProps, componentDidUpdate, getDerivedStateFromProps) in your Recommended component like this:
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
nextProps.getRecommended(nextProps.id);
};
}
You can also add key to your component (which forces it to re-render completely if key changed) like this:
<Recommended key={this.props.match.params.id} id={this.props.match.params.id}/>
You can also use React Hooks to handle this more easily with useEffect:
const Recommended = (props) => {
const { id, getRecommended, recommended } = props;
useEffect(() => {
id && getRecommended(id);
}, [id]);
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
></img>
</Link>
);
})}
</div>
</>
);
};
Note: adding key to component and complete its re-render is not best practice and you should be using Component's lifecycles to avoid it if possible

Nesting components in React, props not passing to child component

I'm creating a list with React components, and am working on the list container and reusable list-item components. The parent most component passes information to middle component, but the child-most component does not have props values.
What am I doing wrong? No console errors.
middle component:
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return (
// want to render list-item component
<li key={video.etag}>{video.snippet.title}</li>
)
});
return (
<ul className="list-group">
{videoItems}
<ListItem
videos={ videoItems }
/>
</ul>
)
}
a console log in child-most component shows no props
I think it will be better if you pass props directly into the children component. Try this:
const VideoList = (props) => {
const videoItems = props.videos.map((video) => {
return (
// want to render list-item component
<ListItem key={video.etag} video={video} />
)
});
return (
<ul className="list-group">
{videoItems}
</ul>
)
}
Inside your children component, you can display what you want
Try passing the property to the child-most component using the 'this' keyword.
<ListItem videos={this.videoItems}/>

Categories

Resources