I have a list of icons. On hover, I'm trying to get the corresponding text to display. Is there a way to do this with a stateless component?
const Socialbar = (props) => {
let spanText;
function socialName(sName) {
spanText = sName;
}
return (
<div>
<i><a onMouseEnter={socialName('Instagram')} onMouseExit={socialName('')} href=""><FontAwesomeIcon="faInstagram" /></a></i>
<h2>{spanText}</h2>
</div>
);
}
import React, {useState} from 'react';
const Socialbar = (props) => {
const [spanText, setSpanText] = useState('');
function socialName(sName) {
if (sName !== spanText) {
setSpanText(sName);
}
}
return (
<div>
<i><a onMouseEnter={() => socialName('Instagram')} onMouseLeave={() => socialName('')} href=""><FontAwesomeIcon="faInstagram" /></a></i>
<h2>{spanText}</h2>
</div>
);
}
Related
I try to change the icon and add it to favorites when I click on it. It works fine, my icon is changed but it impacts all my images icons instead of one. How can I fix this? Here is my code:
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState(false);
useEffect(() => {
...
}, []);
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => setFavorite(!favorite)}>
{favorite ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
Your problem is favorite state is only true/false value, when it's true, all images will have the same favorite value.
The potential fix can be that you should check favorite based on item.id instead of true/false value
Note that I added updateFavorite function for handling favorite state changes on your onClick
Here is the implementation for multiple favorite items
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState([]);
useEffect(() => {
...
}, []);
const updateFavorite = (itemId) => {
let updatedFavorite = [...favorite]
if(!updatedFavorite.includes(itemId)) {
updatedFavorite = [...favorite, itemId]
} else {
updatedFavorite = updatedFavorite.filter(favoriteItem => itemId !== favoriteItem)
}
setFavorite(updatedFavorite)
}
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => updateFavorite(item.id)}>
{favorite.includes(item.id) ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
Here is the implementation for a single favorite item
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState(); //the default value is no favorite item initially
useEffect(() => {
...
}, []);
const updateFavorite = (itemId) => {
let updatedFavorite = favorite
if(itemId !== updatedFavorite) {
updatedFavorite = itemId
} else {
updatedFavorite = null
}
setFavorite(updatedFavorite)
}
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => updateFavorite(item.id)}>
{favorite === item.id ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
This is what you're looking for based on the code you've provided:
import { useState, useEffect } from "react";
const Item = ({ item }) => {
const [favorite, setFavorite] = useState(false);
const toggleFavorite = () => setFavorite((favorite) => !favorite);
return (
<div>
<span>{item.title}</span>
<span>{item.description}</span>
<span onClick={toggleFavorite>
{favorite ? "[♥]" : "[♡]"}
</span>
</div>
);
};
const Row = () => {
const [items, setItems] = useState([]);
useEffect(() => {
// use setItems on mount
}, []);
return (
<div>
{items.map((item) => <Item item={item} key={item.id} />)}
</div>
);
};
export default Row;
You can break up your code into smaller components that have their own states and that's what I did with the logic that concerns a single item. I've created a new component that has its own state (favorited or not).
How would you add a component inside an useRef object (which is refering to a DOM element)?
const Red = () => {
return <div className="color">Red</div>;
};
const Black = () => {
return <div className="color">Black</div>;
};
const Green = () => {
return <div className="color">Green</div>;
};
const Button = (params) => {
const clickHandler = () => {
let boolA = Math.random() > 0.5;
if (boolA) {
params.containerRef.current.appendChild(<Red />);
} else {
let boolB = Math.random() > 0.5;
if (boolB) {
params.containerRef.current.appendChild(<Black />);
} else {
params.containerRef.current.appendChild(<Green />);
}
}
};
return <button onClick={clickHandler}>Click</button>;
};
export default function App() {
const containerRef = useRef(null);
return (
<div className="App">
<Button containerRef={containerRef} />
<div ref={containerRef} className="color-container">
Color components should be placed here !
</div>
</div>
);
}
params.containerRef.current.appendChild(); -> throws an error. I`ve put it to show what I would like to happen.
Also is what I`m doing an anti-pattern/stupid ? Is there another (smarter) way of achieving the above ?
codesandbox link
edit :
I forgot some important information to add.
Only Button knows and can decide what component will be added.
expecting you want to add multiple colors, something like this would work and don't need the ref:
import { useState } from "react";
import "./styles.css";
const Color = () => {
return <div className="color">Color</div>;
};
const Button = (params) => {
return <button onClick={params.onClick}>Click</button>;
};
export default function App() {
const [colors, setColors] = useState([]);
return (
<div className="App">
<Button onClick={() => setColors((c) => [...c, <Color />])} />
<div className="color-container">
{colors}
</div>
</div>
);
}
It's better to have a state that is changed when the button is clicked.
const [child, setChild] = useState(null);
const clickHandler = () => {
setChild(<Color />);
};
const Button = (params) => {
return <button onClick={params.onClick}>Click</button>;
};
<Button onClick={clickHandler} />
<div className="color-container">
Color components should be placed here !
{child}
</div>
Working sandbox
Edit: Refer to #TheWuif answer if you want multiple Colors to be added upon clicking the button repeatedly
There're several things from your code I think are anti-pattern:
Manipulate the real dom directly instead of via React, which is virtual dom
Render the Color component imperatively instead of declaratively
Here's the code that uses useState (state displayColor) to control whether <Color /> should be displayed
import { useState } from "react";
import "./styles.css";
const Color = () => {
return <div className="color">Color</div>;
};
const Button = (props) => {
return <button onClick={props.clickHandler}>Click</button>;
};
export default function App() {
const [displayColor, setDisplayColor] = useState(false);
const clickHandler = () => {
setDisplayColor(true);
};
return (
<div className="App">
<Button clickHandler={clickHandler} />
<div className="color-container">
Color components should be placed here !{displayColor && <Color />}
</div>
</div>
);
}
Codesandbox
I am trying to display the SingleLineText component below that has one input field when a click occurs in the parent component called TextInput. However, after a click, the state is not changed by useState and as a result the child component named SingleLineText is not displayed.
TextInput component is pasted below right after SingleLineText component.
SingleLineText component:
import React from "react";
const SingleLineText = () => {
return(
<form>
<input />
</form>
)
}
export default SingleLineText;
TextInput the Parent component for SingleLineText component:
import React, {useState, useEffect} from "react";
import SingleLineText from "./SingleLineText"
const TextInput = (props) => {
const [showSingleText, setShowSingleText] = useState(false);
useEffect( () => {
}, [showSingleText])
const handleFieldDisplay = (event) => {
if (event.target.value == "Single line text") {
cancel();
setShowSingleText(showSingleText => !showSingleText);
//setShowSingleText(showSingleText => true);
}
}
const cancel = () => {
props.setShowInput(!props.showInput);
}
return (
<>
<div className="dropdown">
<ul key={props.parentIndex}>
{
props.fieldType.map( (val, idx) => {
return(
<option key={idx} value={val} className="dropdown-item" onClick={handleFieldDisplay}> {val} </option>
)
})
}
</ul>
</div>
{showSingleText && <SingleLineText />}
</>
)
}
export default TextInput;
Topmost or grand parent component:
import React, { useState, useEffect, useRef } from "react"
import PropTypes from "prop-types"
import TextInput from "components/pod_table/fields/inputs/TextInput";
const DynamicFields = (props) => {
const fieldType = ['Checkbox', 'Dropdown', 'boolean', 'Single line text'];
const [showDynamicField, setShowDynamicField ] = useState(false);
const[showInput, setShowInput] = useState(false);
useEffect(() => {}, [showDynamicField, showInput]);
const handleShowDynamicField = (event) => {
setShowDynamicField(!showDynamicField);
}
const handleSubDisplay = (event) => {
if (event.target.value == "customise field type") {
setShowDynamicField(!showDynamicField);
setShowInput(!showInput);
}
}
return (
<React.Fragment>
<div>
<i className="bi bi-chevron-compact-down" onClick={handleShowDynamicField}></i>
{ showDynamicField &&
(<div className="dropdown">
<ul key={props.parentIndex}>
{
optionsHash.map( (val, idx) => {
return(
<option key={idx} value={val} className="dropdown-item" onClick={handleSubDisplay}> {val} </option>
)
})
}
</ul>
</div>) }
{showInput && <TextInput fieldType={fieldType} setShowInput={setShowInput} showInput={showInput} /> }
</div>
</React.Fragment>
)
}
The problem is that in the handleFieldDisplayFunction you are calling the cancel function which changes the parent state showInput and hence unmounts the TextInput component itself, so the showSingleText state changes inside TextInput component doesn't have any effect.
Design your code in a way that you are not required to unmount TextInput when you click on an option within it
const handleFieldDisplay = (event) => {
if (event.target.value == "Single line text") {
setShowSingleText(showSingleText => !showSingleText);
}
}
[React] What is the "way" to send/share a function between components?
Better explained in (useless) code
Here I have no problem since everything is in the same component (https://codesandbox.io/s/compassionate-ishizaka-uzlik)
import React, { useState } from "react";
export default function App() {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
const Card = () => <div onClick={letbook}>hey</div>;
const MyCom = () => {
return <div><Card /></div>;
};
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom />
<div>{bookmarks}</div>
</div>
);
}
But then, if now I want to split code, how do I do this? The problem is how to share letbook (this code doesn't work)
import React, { useState } from "react";
export default function App() {
const Card = () => <div onClick={letbook}>hey</div>;
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom />
<div>{bookmarks}</div>
</div>
);
}
const MyCom = () => {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return (
<div>
<Card />
</div>
);
};
I could use a hook that returned the component and the function
const [letbook, MyCom] = useMyCom
But this is not recommended (https://www.reddit.com/r/reactjs/comments/9yq1l8/how_do_you_feel_about_a_hook_returning_components/)
Then I can use a hook and a component, as with the following code, but the code itself seems obfuscated to me, to a point that I doubt whether I should split the code or not
Unless (and this is the question) whether there is a smarter way to do this
import React, { useState } from "react";
export default function App() {
const [bookmarks, setBookmarks, letbook] = useMyCom();
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom card={props => <Card letbook={letbook} />} />
<div>{bookmarks}</div>
</div>
);
}
const Card = ({letbook}) => <div onClick={letbook}>hey</div>;
const useMyCom = () => {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return [bookmarks, setBookmarks, letbook];
};
const MyCom = ({ letbook, card }) => <div>{card(letbook)}</div>;
Split your component to reuse it is definitely a good idea. But make sure your are using and manipulate a single state in the same file an pass it as props. Also, it is important that you avoid to re-render your child component. Only when your main component change props that are necessary to re-render your child component.
import React, { useState, memo } from "react";
const MyCom = memo(props => {
return <div>{props.bookmarks}</div>;
});
export default function App() {
const [bookmarks, setBookmarks] = useState();
const letbook = () => setBookmarks("hello");
return (
<div className="App">
<h1 onClick={letbook}>Hello CodeSandbox</h1>
<MyCom bookmarks={bookmarks} />
</div>
);
}
I wonder if there is not a better way to manage the open and close of Dialogs in a functional component? You can find an example below:
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
const handleDeleteOpen = () => {
setDeleteOpen(true);
};
const handleDeleteClose = () => {
setDeleteOpen(false);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
<EditDialog handleClose={handleEditClose} open={editOpen} />
<DeleteDialog handleClose={handleDeleteClose} open={deleteOpen} />
</>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
I think this is super redundant but I cannot find a nicer way to manage several different dialogs.
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
const handleDeleteOpen = () => {
setDeleteOpen(true);
};
const handleDeleteClose = () => {
setDeleteOpen(false);
};
Many thanks for your time and advice!
To reduce some of the redundancy of your code, you could set the open/close in one function, by essentially toggling the current state. I did mine inline, but you could still create a handleEdit function and toggle the state there.
import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
const [editCard, setEditCard] = useState(false)
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setEditCard(!editCard)}>Toggle Edit</button>
{editCard && <div>Card is open for editing</div>}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is another example with your code. I didn't run it, but it should look something like this.
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEdit = () => {
setEditOpen(!editOpen);
};
const handleDelete = () => {
setDeleteOpen(!deleteOpen);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{
editOpen && <EditDialog handleEdit={handleEdit} />
}
{
deleteOpen && <DeleteDialog handleClose={handleClose} />
}
</>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
The responsibility of open the dialog should be of the main component. This way the modal is only rendered if the state property is true.
Another tip is use <React.Fragment> insted <>
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEditOpen = () => {
setEditOpen(!editOpen);
};
const handleDeleteOpen = () => {
setDeleteOpen(!deleteOpen);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<React.Fragment>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{
editOpen && <EditDialog handleClose={handleEditOpen} />
}
{
deleteOpen && <DeleteDialog handleClose={handleDeleteOpen} />
}
</React.Fragment>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
To incapsulate logic of changing dialog opening state, I'd recommend to create separate hook:
const useToggle = (defaultValue) => {
return useReducer((value) => !value, !!defaultValue)
}
This hook is basically useState but setState function isn't waiting for argument to update state, it updates state with the inverse of current state.
This might be useful while working with dialogs:
const ContactCard = () => {
const [editOpen, toggleEditOpen] = useToggle(false);
const [deleteOpen, toggleDeleteOpen] = usetoggle(false);
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{editOpen && <EditDialog handleEdit={toggleEditOpen} />}
{deleteOpen && <DeleteDialog handleClose={toggleDeleteOpen} />}
</>
);
};