React: changing state onMouseOver - javascript

I'm trying to change a background image state when hovering an element (icon) but I'm always getting the error "TypeError: Cannot read property "icon" of undefined", which is strange, since the icon is working fine until I hover it.
Glad if anyone could help.
States:
this.state = {
images: {
header: "path to img",
icon: "path to icon"
}
}
The method:
handleMouseOver = () => {
this.setState({
images: {
header: "new img"
}
});
};
Header component receiving the image:
<Header bgImg={this.state.images.header} />
Hovered element:
<div>
<img onMouseOver={this.handleMouseOver} src={this.state.images.icon} />
</div>

This issue come from your onMouseOver function, which set a new value for state but remove icon from the images object. You need to run the following:
handleMouseOver = () => {
this.setState((state, props) => {
return {
images: {
header: "new img",
icon: state.images.icon
}
};
});
};

This is because when you are setting your images you are losing icon state. Try like this:
handleMouseOver = () => {
this.setState(prevState => ({
images: {
...prevState.images,
header: "new img"
}
}));
};
You are using the old images by spreading it and then update the necessary property. Why we use setState callback and prevState? Because when we are setting our new state we are depending on our old state.

Related

React component function call only updates one component instance

I have a component called RightTab like this
const RightTab = ({ data }) => {
return (
<div className="RightTab flex__container " onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};
export default RightTab;
The tab has an active state in its CSS like this
.RightTab.active {
background-color: var(--primaryGreen);
}
as you have seen it changes the color when an active class is added. I have an array in the parent component that I pass down to the child component as props. Here is the array
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleDashBoardClick,
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleInventoryClick,
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleReportsClick,
},
];
Here is how I pass the props down.
<RightTab data={dataArray[0]} />
<RightTab data={dataArray[1]} />
<RightTab data={dataArray[2]} />
The data prop passed into the component is an object containing a function call as one of its properties like this. I have an onclick attribute on the child components' main container that is supposed to call the respective function.
The function is what adds the active class to make the background change color. However each time I click on the component it only changes the background of the first occurrence. And as you may have noticed I call the component thrice. No matter which component I click only the first ones background changes.
Here is an example of the function that is on the prop object.
const handleDashBoardClick = () => {
const element = document.querySelector(".RightTab");
element.classList.toggle("active");
};
I don't get what I'm doing wrong. What other approach can I use?
Although you use the component 3 times, it doesn't mean that a change you make in one of the components will be reflected in the other 2, unless you specifically use a state parameter that is passed to all 3 of them.
Also, the way you add the active class is not recommended since you mix react with pure js to handle the CSS class names.
I would recommend having a single click handler that toggles the active class for all n RightTab components:
const MainComponent = () => {
const [classNames, setClassNames] = useState([]);
const handleClick = (name) =>
{
const toggledActiveClass = classNames.indexOf('active') === -1
? classNames.concat(['active'])
: classNames.filter((className) => className !== 'active');
setClassNames(toggledActiveClass);
switch (name) {
case 'Dashboard';
// do something
break;
case 'Inventory':
// ....
break;
}
}
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleClick.bind(null, 'Dashboard'),
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleClick.bind(null, 'Inventory'),
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleClick.bind(null, 'Reports'),
},
];
return (
<>
{dataArray.map((data) =>
<RightTab key={data.name}
data={data}
classNames={classNames} />)}
</>
);
};
const RightTab = ({ data, classNames }) => {
return (
<div className={classNames.concat(['RightTab flex__container']).join(' ')}
onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};

I'm using CDN links of intro.js, I want to implement "don't show again checkbox on Intro.js pop-up", how can we do that?

We have a requirement where I have to add a checkbox on the intro.js pop-up,
so I have used intro.js min and CSS files CDN links to use the Intro.js,
currently, I'm getting intro.js pop-up with the intro and checkbox,
Issues I am facing are:
If I am using checkboxes on multiple steps then I am not able to fetch the checkbox value, its works in the case of one step
Is there any other way to add a checkbox in intro.js
when I click the checkbox and click on the done button the value of the checkbox should be passed to the backend
I Have tried something like.
Reactjs code
const HomePage: React.FunctionComponent<Props> = ({ id }: Props) => {
const [checkTour, setCheckTour] = React.useState(false);
React.useEffect(() => {
if ((window as any).introJs) {
(window as any)
.introJs()
.setOptions({
disableInteraction: true,
exitOnEsc: false,
exitOnOverlyClicking: false,
overlayOpacity: 0.7,
showBullets: false,
showProgress: false,
showStepNumbers: false,
tooltipClass: "customTooltip",
steps: [
{
title: "Welcome",
intro: `Hello World! 👋 `,
element: document.querySelector("#logoHeighlight"),
tooltipClass: "welcomeTooltip",
},
{
intro: "<style>{color: 'red';};</style>step 1"! 👋 `,
element: document.querySelector("#cart"),
},
{
intro: `Go to plp page <label for='donotShowMeAgain'> Do Not Show Me Again </label><input type='checkbox' id='donotShowMeAgain'>`,
element: document.querySelector("#HeaderSignIn"),
},
],
})
.onbeforeexit(function () {
// eslint-disable-next-line no-console
console.log("before Done Calling");
})
.start();
const doneTour = document.getElementById("donotShowMeAgain");
doneTour?.addEventListener("click", donotShowMeAgainClicked);
return () => {
doneTour?.removeEventListener("click", donotShowMeAgainClicked);
};
}
}, []);
const donotShowMeAgainClicked = (e: any) => {
setCheckTour(e.target.checked);
};
// eslint-disable-next-line no-console
console.log("checkTour", checkTour);
return (
<Page data-test-selector="homePage" {...styles.homePageContainer} className="homePageContainer">
<Zone contentId={id} zoneName="Content" requireRows />
<AddToListModal />
</Page>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.0/umd/react-dom.production.min.js"></script>

React update array prop from child to parent

Hello I am struggling to properly update my state from my child component to my parent.
Basically I am trying to set the current state to true onclick.
This is my parent component:
export default function Layout({ children }: Props) {
const [navigation, setNavigation] = useState([
{ name: 'Dashboard', href: '/', icon: HomeIcon, current: true },
{ name: 'Create Fact', href: '/facts/create', icon: UsersIcon, current: false },
{ name: 'Documents', href: '/documents', icon: InboxIcon, current: false }
])
return (
<>
<Sidebar navigation={navigation} setNavigation={setNavigation} />
This is my child Component (Sidebar)
type Props = {
navigation: Array<{
name: string
href: string
icon: any
current: boolean
}>
setNavigation: (
navigation: Array<{
name: string
href: string
icon: any
current: boolean
}>
) => void
}
const Sidebar = ({navigation, setNavigation}: Props) => {
const router = useRouter()
const toggleNavigation = (name: string) => {
// todo: Here I would like to properly update the state with the current selected navigation item (current)
const newNavigation = navigation.map(nav => {
if (nav.name === name) {
nav.current = true
return nav
}
})
}
return (
<nav className="flex-1 px-2 pb-4 space-y-1">
{navigation.map(item => (
<span
onClick={() => toggleNavigation(item.name)}
There are three problems:
You never call setNavigation with your new array.
You don't clear current on the formerly-current item.
Although you're creating a new array, you're reusing the objects within it, even when you change them, which is against the Do Not Modify State Directly rule.
To fix all three (see *** comments):
const toggleNavigation = (name: string) => {
const newNavigation = navigation.map(nav => {
if (nav.name === name) {
// *** #3 Create a *new* object with the updated state
nav = {...nav, current: true};
} else if (nav.current) { // *** #2 make the old current no longer current
nav = {...nav, current: false};
}
return nav;
});
// *** #1 Do the call to set the navigation
setNavigation(newNavigation);
};
Separately, though, I would suggest separating navigation out into two things:
The set of navigation objects.
The name of the current navigation item.
Then setting the navigation item is just setting a new string, not creating a whole new array with an updated object in it.
T.J. Crowder's solution and explanation are great.
Additionally, you can write that logic in a shorter syntax. Just a preference.
const newNavigation = navigation.map(nav => {
return nav.name === name
? { ...nav, current: true }
: { ...nav, current: false }
})

WP Blocks save function doesn't match edit function when editor loads

I'm developing a WP Gutenberg block based on https://github.com/JimSchofield/Guty-Blocks-2 and I'm running into an issue where the saved content doesn't match the editor when loaded therefore I'm seeing an error 'This block contains unexpected or invalid content'.
I have tried looking in the browser console but I can't figure out where the discrepancy is, both the edit and save functions reference the images but they're not being stored by the save function.
It's worth noting that once the block is loaded for the first time, used and the post is saved it works correctly on the front-end. It's when you go back to the editor it doesn't work anymore.
import './__block__.view.scss';
import './__block__.editor.scss';
const {
registerBlockType,
getBlockDefaultClassName
} = wp.blocks;
const {
InspectorControls,
MediaUpload
} = wp.editor;
const {
Button
} = wp.components;
registerBlockType('__namespace__/__block__', {
title: '__prettyname__(noCase)',
icon: '__icon__',
category: '__category__',
attributes: {
imgUrl: {
type: 'array',
source: 'children',
selector: 'img',
},
},
edit({ attributes, className, setAttributes }) {
//Destructuring the images array attribute
const {images = []} = attributes;
// This removes an image from the gallery
const removeImage = (removeImage) => {
//filter the images
const newImages = images.filter( (image) => {
//If the current image is equal to removeImage the image will be returnd
if(image.id != removeImage.id) {
return image;
}
});
//Saves the new state
setAttributes({
images:newImages,
})
}
//Displays the images
const displayImages = (images) => {
return (
//Loops throug the images
images.map( (image) => {
return (
<div className="gallery-item-container">
<img className='gallery-item' src={image.url} key={ images.id } />
<div className='remove-item' onClick={() => removeImage(image)}><span class="dashicons dashicons-trash"></span></div>
<div className='caption-text'>{image.caption[0]}</div>
</div>
)
})
)
}
//JSX to return
return (
<div>
<MediaUpload
onSelect={(media) => {setAttributes({images: [...images, ...media]});}}
type="image"
multiple={true}
value={images}
render={({open}) => (
<Button className="select-images-button is-button is-default is-large" onClick={open}>
Add images
</Button>
)}
/>
<br />
<div class="modal__img">
<div class="flexslider">
<ul class="slides" data-total-slides={images.length}>{ displayImages(images) }</ul>
</div>
</div>
</div>
);
},
save({attributes}) {
// Destructuring the images array attribute
const { images = [] } = attributes;
// Displays the images
const displayImages = (images) => {
return (
images.map( (image,index) => {
return (
<li><img
className='lazy'
key={images.id}
data-src={image.url}
data-slide-no={index}
data-caption={image.caption[0]}
alt={image.alt}
/></li>
)
})
)
}
//JSX to return
return (
<div class="modal__img">
<div class="flexslider">
<ul class="slides" data-total-slides={images.length}>{ displayImages(images) }</ul>
</div>
</div>
);
},
});
I expected the block to output the original HTML when back in the editor, but this behaviour does not work.
In both the save and edit function your are referencing images from the attributes prop. Yet, when you register your block and set up the attributes, you only have imageUrl as an attribute. This means images are never getting stored in the DB, and do not exist when you come back to edit.
Adding images as a attribute should fix this.
What you have
attributes: {
imgUrl: {
type: 'array',
source: 'children',
selector: 'img',
},
},
What it should be
attributes: {
images: {
type: 'array',
default: []
},
},
Try passing props instead of attributes in edit and save functions, and then simply use
var attributes = props.attributes;
For more reference read the code in these examples.

Generic method to render React component depends on two props

I've got a component that gets two props, function and node(or string with label text), depends on these props I render the icon with some label. In future, I'm going to add more button and want to create the generic method that rendered this icon more flexible. So how to create such generic method for that?
const Wrapper = ({onRefresh, onExportToExcel, actionsLabel}) => {
return
{onRefresh && !!actionsLabel.refresh &&
<InlineIconButton name='refresh' label={actionsLabel.refresh} onClick={onRefresh} icon={<Autorenew/>} aria-label="Refresh"/>}
{onExportToExcel && !!actionsLabel.exportToExcel &&
<InlineIconButton name='exportToExcel' label={actionsLabel.exportToExcel} onClick={onExportToExcel} icon={<FileDownload/>} aria-label="ExportToExcel"/>}
}
<Wrapper onRefresh={()=> {}} onExportToExcel ={()=> {}} actionLabel={refresh: 'refresh', exportToExcel: 'export'}>
Maybe do something like:
const EXPORT_EXCEL = {
key: "EXPORT_EXCEL",
label: "export",
ariaLabel: "Export Excel",
icon: <Autorenew/>,
handler: params => { /* your function */ }
};
const REFRESH = {
key: "REFRESH",
label: "refresh",
ariaLabel: "Refresh",
icon: <FileDownload/>,
handler: params => { /* your function */ }
};
<Wrapper type={EXPORT_EXCEL} />;
const Wrapper = ({ type }) => {
return <InlineIconButton name={type.key} label={type.label} onClick={type.handler} icon={type.icon} aria-label={type.ariaLabel ? type.ariaLabel : type.label} />;
}
}
You even the possiblity to throw those EXPORT_EXCEL and REFRESH into array. Instead of having them loose put them in an array like so:
const BUTTONS = [
{
key: "EXPORT_EXCEL",
label: "export",
ariaLabel: "Export Excel",
icon: <Autorenew/>,
handler: params => { /* your function */ }
},
{
key: "REFRESH",
label: "refresh",
ariaLabel: "Refresh",
icon: <FileDownload/>,
handler: params => { /* your function */ }
},
];
And then loop through to create the Wrapper.
But then it's really up to you and your preferences and app's requirements
The entire idea behind React is to be able to create a unique component for every kind of usage. That is the entire philosophy behind React composability. Don't understand why would you want to wrap it.

Categories