I'm building a React tab navigation component with emotion. I'm having trouble finding a solution that would allow me to:
Initially hide all content except for the buttons and not style the buttons.
When you click on a button activate the style and show the content associated with that button.
And finally when you click outside or the input is empty reset to initial state.
Here is the code:
Code
import React, { useState } from "react";
import ReactDOM from "react-dom";
import styled from "#emotion/styled";
import "./styles.css";
const StyledShowButton = styled("button", {
shouldForwardProp: (prop) => ["active"].indexOf(prop) === -1
})`
color: ${({ active }) => (active ? "red" : "black")};
`;
function App() {
const [active, setActive] = useState(0);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
console.log("Reset Everyting");
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={0}
active={active === 0}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active === 0 ? (
<input placeholder="First input" onChange={handleInputChange} />
) : (
<input placeholder="Second input" onChange={handleInputChange} />
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Just ask if I didn't make my self clear enough,
Thanks beforehand!
Erik
You can hide inpts in this way at first by assigning a null value to the active state.
You can also initialize values from 1 so that id and state state are not confused.
I made the arrangements.
You can review the code below.
You can also view it from this link. Code:
function App() {
const [active, setActive] = useState(null);
const [showInput, setShowInput] = useState(false);
const handleInputChange = (e) => {
if (e.target.value < 1) {
setActive(null);
}
};
const handleTabClick = (e) => {
const index = parseInt(e.target.id, 0);
if (index !== active) {
setActive(index);
}
if (!showInput) {
setShowInput(!showInput);
}
};
return (
<div className="App">
<StyledShowButton
type="button"
id={1}
active={active === 1}
onClick={handleTabClick}
>
First
</StyledShowButton>
<StyledShowButton
type="button"
id={2}
active={active === 2}
onClick={handleTabClick}
>
Second
</StyledShowButton>
{/* CONTENT */}
{active &&
(active === 1 ? (
<>
<input placeholder="First input" onChange={handleInputChange} />
</>
) : (
<input placeholder="Second input" onChange={handleInputChange} />
))}
</div>
);
}
Related
I need a search box in React that opens a dropdown as soon as an entry is made. The dropdown should contain buttons that can trigger an event.
The problem is that the dropdown has to disappear as soon as another input box is used in the application.
I could also implement this, but now I have the problem that the event of the button in question is not triggered because the focus of the input field is lost beforehand as soon as I press the button. As a result, the dropdown disappears and the event is never triggered.
This is roughly my Searchbox Component:
import React, { useState } from 'react';
import Dropdown from './Dropdown';
function Search(props) {
const [focused, setFocused] = useState(false);
const inputHandler = (params) => {
if (params.length > 0)
props.apiCall(params);
}
const buttonHandler = (id) => {
console.log(id);
}
return (
<>
<input
type="text"
placeholder="Suchen.."
onChange={(event) => inputHandler(event.target.value)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)} // Problem area
/>
{
focused === true && props.apiData.length > 0 ?
props.apiData.map((mappedData, key) => {
return (
<Dropdown
key={key}
id={mappedData.id}
name={mappedData.name}
/*
even more Data
*/
buttonHandler={buttonHandler}
/>
)
})
: null
}
</>
)
}
export default Search;
This is my Dropdown Component:
import React from 'react';
function Dropdown(props) {
return (
<ul key={props.id}>
<li>{props.name}</li>
<li>even more data</li>
<li>
<input
type="button"
value="Select"
onClick={() => {
props.buttonHandler(props.id)
}}
/>
</li>
</ul>
)
}
export default Dropdown;
To resolve this issue blur event in Search Component can be handled with delay.
function Search(props) {
const [focused, setFocused] = useState(false);
const inputHandler = (params) => {
if (params.length > 0) props.apiCall(params);
};
const buttonHandler = (id) => {
console.log(id);
};
return (
<>
<input
type="text"
placeholder="Suchen.."
onChange={(event) => inputHandler(event.target.value)}
onFocus={() => setFocused(true)}
- onBlur={() => setFocused(false)} // Problem area --> remove this line
+ onBlur={() => setTimeout(()=> setFocused(false),500)} // ---> delay added
/>
{focused === true && props.apiData.length > 0
? props.apiData.map((mappedData, key) => {
return (
<Dropdown
key={key}
id={mappedData.id}
name={mappedData.name}
/*
even more Data
*/
buttonHandler={buttonHandler}
/>
);
})
: null}
</>
);
}
I've created a simple star rating component to make users able to review my books.
Here's the component:
import React, { useState } from 'react'
import { FaStar } from 'react-icons/fa'
const StarRating = (props) => {
const [rating, setRating] = useState(null);
return (
<Wrapper>
{[...Array(5)].map((star, i) => {
const ratingValue = i + 0;
return (
<label>
<input
type="radio"
name="rating"
onClick={() => setRating(props.ratingValue)}
/>
<FaStar color={ratingValue < rating ? "#01af93" : "#bbb"} />
</label>
)
})}
</Wrapper>
)
}
export default StarRating
So, if somebody clicks on the Stars the rating will appear (using an onClick handler).
I would like to display the ratings without the onClick handler now.
I've tried simply to add value={props.ratingValue} instead of onClick={() => setRating(props.ratingValue)} but it doesn't work.
Hope someone can help with what I'm doing wrong.
You have to move onClick handler and value to the parent container class. So changing state and keeping current input value must be done in your parent container. Below I share a code snippet for your sample.
import React, { useState } from "react";
import { FaStar } from "react-icons/fa";
const StarRating = (props) => {
console.log(props);
return (
<div>
{Array(5)
.fill(0)
.map((_, idx) => (
<label key={idx}>
<input
type="radio"
name="rating"
onChange={() => props.setRating(idx)}
value={props.ratingValue}
checked={idx === props.ratingValue}
/>
<FaStar color={idx < 3 ? "#01af93" : "#bbb"} />
</label>
))}
</div>
);
};
export const RatingContainer = () => {
const [rate, setRate] = useState(3);
return (
<div>
<StarRating setRating={(val) => setRate(val)} ratingValue={rate} />
</div>
);
};
I want to add to Chip an startIcon={<Icon />}
when click on a Chip.
The state of the icon is managed by chipsState.
In this code,
the state of all chips would change.
How can I change only the chipsState of the element that is clicked?
In this code, the state of all chips will change.
How can I change only the chipsState of the element that is clicked?
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
const [chipsState, setChipsState] = useState(false);
const onChipClick = (element:any) => {
setChipsState(chipsState => !chipsState);
}
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<Chip onClick={() => onChipClick(element)} startIcon={chipsState && <Icon />}>{element.description}</Chip>
</div>
))}
</div>
);
}
export default Modal;
To handle local state (and better testing), you should create a new custom Chip component with dedicated chipState.
interface CustomChipProps {
description: string
}
const CustomChip = (props: CustomChipProps) => {
const [chipState, setChipState] = useState(false);
return <Chip onClick={() => setChipState(prev => !prev)} startIcon={chipState && <Icon />}>{props.description}</Chip>;
}
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<CustomChip description={element.description} />
</div>
))}
</div>
);
}
export default Modal;
You can achieve your desired output by changing chipState state from boolean to object.
So first let's change to object state instead of boolean
const [chipsState, setChipsState] = useState({});
Now we will change onChipClick function to change value of selected chip state
const onChipClick = (element:any) => {
setChipsState({...chipsState, chipsState[element]: !chipsState[element]});
}
And finally we will read correct value of each chipsState element.
<Chip onClick={() => onChipClick(element)} startIcon={chipsState[element] && <Icon />}>{element.description}</Chip>
You can try like the following
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
const ChipSet = ({ symtomsData }) => {
const data = symtomsData.map((symtom) => ({ ...symtom, isSelcted: false }));
const [chipSets, setChipSets] = useState(data);
const onSelectChipSet = useCallback(
(e, index) => {
const updatedChipSets = chipSets.map((chip, i) =>
i === index ? { ...chip, isSelcted: e.target.checked } : chip
);
setChipSets(updatedChipSets);
},
[chipSets]
);
console.log("chipSets", chipSets);
return (
<div>
<h1>Symtoms Data</h1>
{chipSets.map((x, i) => (
<div key={i}>
<label>
<input
onChange={(e) => onSelectChipSet(e, i)}
type="checkbox"
value={x.isSelcted}
/>
{x.description}
</label>
</div>
))}
</div>
);
};
class App extends React.Component {
render() {
const symtomsData = [
{
description: "mild"
},
{
description: "cold"
}
];
return (
<Grid>
<Row>
<ChipSet symtomsData={symtomsData} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I have two self-defined class components, from which I create two instances like:
<PersonAddModal details={details} open={open} setOpen={setOpen} addFather={false}/>
So the addFather property is different, the rest is the same.
The PersonAddModal looks as follows. (The Add father/Add mother text is displayed correctly though, depending on the val of addFather)
import React from 'react'
import { Modal } from 'react-responsive-modal';
class PersonAddModal extends React.Component
{
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event)
{
//...
}
getParentTypeString()
{
return this.props.addFather ? "father" : "mother";
}
render() {
return(
<div>
<button onClick={() => this.props.setOpen(true)}>{this.props.addFather ? <div>Add father</div>:<div>Add mother</div>}</button>
<Modal open={this.props.open} onClose={() => this.props.setOpen(false)}>
<h1>Add a {this.getParentTypeString()} of {this.props.details.firstName}</h1>
<form onSubmit={(event) => {this.handleSubmit(event)}}>
<input type="text" id="firstName" name="firstName" placeholder="Enter first name" required/>
<input type="text" id="lastName" name="lastName" placeholder="Enter last name" required/>
<button type="submit">Submit</button>
</form>
</Modal>
</div>
)};
}
export default PersonAddModal;
I really don't understand why (apparently) the val of addFather of the latest added component seems also to be used for the first component. Aren't they supposed to be independant from each other? Help is graetly appreciated!
Edit:
They;re being used as follows:
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import 'react-responsive-modal/styles.css';
import { Modal } from 'react-responsive-modal';
import PersonLink from './PersonLink'
import PersonAddModal from './PersonAddModal'
const PersonDetails = ({ match }) => {
const [details, setDetails] = React.useState({});
const [isLoading, setIsLoading] = React.useState(true);
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
fetch(`https://localhost:44332/api/person/${match.params.id}/details`)
.then(response => response.json())
.then((data) => { setDetails(data); setIsLoading(false); });
}, [match.params.id])
return (
<>
{
(isLoading || details.id == null) ? <h1>Loading..</h1>
:
<>
<h1>Person Details of {details.firstName} {details.lastName}</h1>
<h2>Parents </h2>
{
details.father != null && details.father != 0 ?
<PersonLink id={details.father } />
:
<>
<PersonAddModal details={details} open={open} setOpen={setOpen} addFather={true}/>
</>
}
{
details.mother != null && details.mother != 0 ?
<PersonLink id={details.mother} />
:
<PersonAddModal details={details} open={open} setOpen={setOpen} addFather={false}/>
}
{details.spouse != 0 ?
<>
<h2>Spouse</h2>
<PersonLink id={details.spouse}/>
</>
: <></>}
<h2>Siblings</h2>
{
details.siblings.map((sibling) => (<PersonLink id={sibling} />))
}
<h2>Children</h2>
{
details.children.map((child) => (<PersonLink id={child} />))
}
</>
}
</>
);
};
export default PersonDetails;
I've found the answer:
As you could see, I was creating two Modals, and my issue was that both were displaying the same. Turns out that because I was giving them both the same state (open/setOpen), no matter on which I clicked, always the same was opening.
I have a small webapp and I need to display a dropdown menu when you hover over the word "collective" but the menu should be still there untill I click on a word in the menu or hover out of the menu but it instantly disappears when I move my mouse out from the word "collective" and I don't even get to move my mouse pointer to any of the drop down menu item. I want my drop down menu to be like the one in https://www.geeksforgeeks.org/
my sandbox so far
https://codesandbox.io/s/funny-river-c76hu
For the app to work, you would have to type in the input box "collective", click analyse, then a progressbar will show, click on the blue line in the progressbar, an underline would show under the word "collective" then you should hover over "collective" word and a drop down menu should be displayed.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Content, Dropdown, Label, Progress, Button, Box } from "rbx";
import "rbx/index.css";
function App() {
const [serverResponse, setServerResponse] = useState(null);
const [text, setText] = useState([]);
const [loading, setLoading] = useState(false);
const [modifiedText, setModifiedText] = useState(null);
const [selectedSentiment, setSentiment] = useState(null);
const [dropdownContent, setDropdownContent] = useState([]);
const [isCorrected, setIsCorrected] = useState(false);
const [displayDrop, setDisplayDrop] = useState(false);
useEffect(() => {
if (serverResponse && selectedSentiment) {
const newText = Object.entries(serverResponse[selectedSentiment]).map(
([word, recommendations]) => {
const parts = text[0].split(word);
const newText = [];
parts.forEach((part, index) => {
newText.push(part);
if (index !== parts.length - 1) {
newText.push(
<u
className="dropbtn"
data-replaces={word}
onMouseOver={() => {
setDropdownContent(recommendations);
setDisplayDrop(true);
}}
onMouseOut={() => setDisplayDrop(false)}
>
{word}
</u>
);
}
});
return newText;
}
);
setModifiedText(newText.flat());
}
}, [serverResponse, text, selectedSentiment]);
const handleAnalysis = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setServerResponse({ joy: { collective: ["inner", "constant"] } });
}, 1500);
};
const handleTextChange = event => {
setText([event.target.innerText]);
};
const replaceText = wordToReplaceWith => {
const replacedWord = Object.entries(serverResponse[selectedSentiment]).find(
([word, recommendations]) => recommendations.includes(wordToReplaceWith)
)[0];
setText([
text[0].replace(new RegExp(replacedWord, "g"), wordToReplaceWith)
]);
setModifiedText(null);
setServerResponse(null);
setIsCorrected(true);
setDropdownContent([]);
};
const hasResponse = serverResponse !== null;
return (
<Box>
<Content>
<div
onInput={handleTextChange}
contentEditable={!hasResponse}
style={{ border: "1px solid red" }}
>
{hasResponse && modifiedText
? modifiedText.map((text, index) => <span key={index}>{text}</span>)
: isCorrected
? text[0]
: ""}
</div>
<br />
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
{dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))}
</div>
) : null}
<br />
<Button
color="primary"
onClick={handleAnalysis}
disabled={loading || text.length === 0}
>
analyze
</Button>
<hr />
{hasResponse && (
<Label>
Joy{" "}
<Progress
value={Math.random() * 100}
color="info"
onClick={() => setSentiment("joy")}
/>
</Label>
)}
</Content>
</Box>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);