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
Related
I have a screen (parent) where a FlatList resides in, and the renderItem shows a Child element.
In this Child element, I have a Pressable, and when the user clicks on it, it shows a Checked Icon and changes its background colour.
This happens dynamically based off a state Array in the Parent where in each renderItem of the Child I pass the state Array.
And in the Child component I check if the ID of this Child element is present, if it is, a Checked Icon is shown and the background changes colour.
I know that states in React is asynchronous but I'm always having problems working through such scenarios.
I have tried checking in the Parent screen where the FlatList resides at, to instead pass a Boolean prop to the Child on whether to show the Checked Icon.
E.g. (Sorry always having trouble formatting code in SO)
<FlatList
data={displayData}
renderItem={({item}) => (
<Child
key={item}
userData={item}
id={item}
isSelected={selectedIds?.includes(item)}
// selectedIds={selectedIds}
selectedHandler={id => selectedHandler(id)}
/>
)}
keyExtractor={item => item}
/>
instead of
// In Parent Screen
<FlatList
data={displayData}
renderItem={({item}) => (
<Child
key={item}
userData={item}
id={item}
selectedIds={selectedIds} // here
selectedHandler={id => selectedHandler(id)}
/>
)}
keyExtractor={item => item}
/>
// In Child element
const Child = ({
id,
selectedIds,
selectedHandler
}) => {
return (
<Pressable
style={[
styles.checkContainer,
selectedIds?.includes(id) && { backgroundColor: '#3D9A12' }
]}
onPress={onPressHandler}
>
{selectedIds?.includes(id) && <CheckIcon />} {/* Problem lies here. Not showing Checked Icon */}
</Pressable>
);
};
I won't dump any code here as I have made a snack of the reproduction of my problem.
I appreciate any help please. Thank you so much
Unchecked:
Checked:
The problem is in the selectedHandler function.
You are storing the reference of your state in this variable.
let selectedArr = selectedIds;
and later directly modifying the state itself by doing so:
selectedArr.push(id);
This is why the state updation is not firing the re-render of your component.
Instead, what you need to do is:
let selectedArr = [...selectedIds];
By spreading it, you will be storing a copy of your array and not a reference to it. Now if you modify selectedArr, you won't modifying your state.
I made the changes in the snack provided by you and it now works fine.
The updated selectedHandler function:
const selectedHandler = id => {
let selectedArr = [...selectedIds];
console.log('before selectedArr', selectedArr);
if (selectedArr.includes(id)) {
selectedArr = selectedArr.filter(userId => userId !== id);
setSelectedIds(selectedArr);
console.log('after selectedArr', selectedArr);
return;
}
if (selectedArr.length > 2) {
selectedArr.shift();
}
selectedArr.push(id);
console.log('after selectedArr', selectedArr);
setSelectedIds(selectedArr);
};
I am trying to implement a function that sets a property of the state, "changedMarkup" on a button click event.
Constructor
constructor() {
super();
this.state = {
value: 0,
changedMarkup: 0
};
}
Render
render() {
const { classes } = this.props;
return (
<Paper className={styles.root}>
<Tabs
value={this.state.value}
onChange={this.handleChange}
variant="fullWidth"
indicatorColor="primary"
textColor="primary"
aria-label="icon label tabs example"
>
<Tab onClick={() => this.changeMarkup(0)} icon={<TrendingUpIcon />} label="TRENDING" />
<Tab onClick={() => this.changeMarkup(1)} icon={<ScheduleIcon />} label="NEW" />
<Tab onClick={() => this.changeMarkup(2)} icon={<WhatshotIcon />} label="HOT" />
</Tabs>
</Paper>
);
}
changeMarkup function
changeMarkup = (markup) => {
this.setState({
changedMarkup: markup
})
console.log("markup", this.state.changedMarkup);
}
Expected behavior
Log statement when the first tab is clicked: markup 0
Log statement when the second tab is clicked: markup 1
Log statement when the third tab is clicked: markup 2
Resulting behaviour
The "changeMarkup" property produces unexpected values. I can't seem to find an exact common pattern but it seems to be increment from 0 to 2 and decrements back to 0 with continuous clicks irrespective of the tab clicked
Any help is appreciated.
See: https://reactjs.org/docs/react-component.html#setstate
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
setState is an async operation, it won't be complete at the time you do your console logging. You can supply a callback after it has been updated:
this.setState({changedMarkup: markup}, () => {
// Do your logging here!
});
Because this.setState({}) is async operation so if you want updated value log than you can do it in two ways.
1. use callback function like this
this.setState({
//set your state
}, () => {
console.log('your updated state value')
})
2. in render function like this
render(){
console.log('your state')
return()
}
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
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.
I'm using gatsby and have a functional component that loops through some data to create radio button group with an onchange event and checked item. When i update the state whole page component rerenders. i though adding memo was meant to stop this but it doesn't seem to work.
here is the code
const BikePage = React.memo(({ data }) => {
console.log("page data", data)
const [selectedColor, setColor] = useState(data.bike.color[0])
const onColorChange = e => {
setColor(e.target.value)
}
return (
<div>
{data.treatment.price.map((value, index) => {
return (
<div>
<input
id={`bike-option-${index}`}
name="treatment"
type="radio"
value={value}
checked={selectedColor === value}
onChange={e => onColorChange(e)}
/>
<label
htmlFor={`treatment-option-${index}`}
>
{value}
</label>
</div>
)
})}
<Link
to="/book"
state={{
bike: `${data.bike.title}-${selectedColor}`,
}}
className="c-btn"
>
Book Now
</Link>
</div>
)
});
If you update the state the component will re-render, that's fundamentally how react works. the memoised data prop is coming from outside of the component.
"If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result" react.memo
You're not changing the incoming props though, you're changing the state
Side note: i imagine that on changing this value you probably want to be changing the state of the data on the server through some means also ( REST POST / graphql mutation). Subsequent refetches of this data would re-render this component as well. It depends what you're trying to ultimately achieve.