Changing icons for Segment In React Js - reactjs

I am implementing a Segment bar for React Js Using a functional component. currently i am changing the color of the highlighted button index.
This is my code for Segment
import React, { useState } from "react";
import "./segment.css";
const buttons = [
{ name: "Bat" },
{ name: "Ball" },
{ name: "Tennis" },
{ name: "Shuttle" },
];
const Segment = () => {
const [activeIndex, setActiveIndex] = useState(0);
return (
<div>
{buttons.map((u, i) => (
<button
className={i === activeIndex ? "segment_active" : "segment_inActive"}
onClick={() => setActiveIndex(i)}
key={u.name}
>
{u.name}
</button>
))}
</div>
);
};
export default Segment;
The code above one is working as expected but now I need to put an icon instead of a name and making it highlight.
my icon names are Bat_active and Bat_InActive etc..
Can someone let me know that how to change the Active icon on the button click?

Related

React-select not displaying input when modifying value prop using state hooks

Ive been trying to make a dynamic input field(more input options appear on user input) with react-select, the input gets displayed when I'm not modifying value prop using state variables, but when I modify value prop using state hooks it is not displaying anything.
Here is the code snippet for without hooks which displays output just fine
import React,{useState} from "react"
import CreatableSelect from 'react-select/creatable';
export default function DynamicInput(){
const [val,setVal] = useState([])
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
const handleAdd=()=>{
const tempVal = [...val,[]]
setVal(tempVal)
}
const handleDel=(indx)=>{
const deleteVal = [...val]
deleteVal.splice(indx,1);
setVal(deleteVal);
}
return (
<div>
<button onClick={()=>handleAdd()}>Add</button>
{val.map((data,indx)=>{
return(
<div key = {indx}>
<CreatableSelect isClearable options={options} placeholder="Placeholder" } />
<button onClick = {()=>handleDel(indx)}>X</button>
</div>
)
})
}
</div>
);
}
Now I tried to use hooks to handle the input hooks.
import React, { useState } from "react";
import CreatableSelect from "react-select/creatable";
export function DynamicInputwHooks() {
const [val, setVal] = useState([]);
const [selectedOp, setSelectedOp] = useState([]);
const options = [
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
];
const handleAdd = () => {
const tempVal = [...val, []];
const tempSel = [...selectedOp, []];
setSelectedOp(tempSel);
setVal(tempVal);
};
const handleSelection = (v, indx) => {
const tempSel = [...selectedOp];
tempSel[indx] = v.value;
setSelectedOp(tempSel);
};
const handleDel = (indx) => {
const deleteVal = [...val];
const deletesel = [...selectedOp];
deleteVal.splice(indx, 1);
deletesel.splice(indx, 1);
setVal(deleteVal);
setSelectedOp(deletesel);
};
return (
<div>
<button onClick={() => handleAdd()}>Add</button>
{val.map((data, indx) => {
return (
<div key={indx}>
<CreatableSelect
isClearable
options={options}
placeholder={"Placeholder"}
value={selectedOp[indx]}
onChange={(e) => handleSelection(e, indx)}
/>
<button onClick={() => handleDel(indx)}>X</button>
</div>
);
})}
</div>
);
}
I also added an input box with the same value and it displays value accordingly.
(I am unable to embed code using sandbox but this is the link : https://codesandbox.io/s/frosty-darwin-s3ztee?file=/src/App.js)
Upon using inspect element I found that when not using handling value there is an additional div as compared to when modifying value.
When not handling value prop
The single value div
when handling value prop
No single value div gets created
I cannot use useRef as there can be multiple inputs.
any help how to solve this would be appreciated thanks.
Ok so after some more searching I found this sandbox example that helps solve the problem
https://codesandbox.io/s/react-select-set-value-example-d21pt?file=/src/App.js
while this works for react-select but using it for react-select creatable requires appending to the options based on isNew prop of input when creating a new option.

React Button Multi-Select, strange style behaviour

I am trying to create a simple button multi-select in React but I'm currently getting unexpected behaviour. I'd like users to be able to toggle multiple buttons and have them colourise accordingly, however the buttons seem to act a bit randomly.
I have the following class
export default function App() {
const [value, setValue] = useState([]);
const [listButtons, setListButtons] = useState([]);
const BUTTONS = [
{ id: 123, title: 'button1' },
{ id: 456, title: 'button2' },
{ id: 789, title: 'button3' },
];
const handleButton = (button) => {
if (value.includes(button)) {
setValue(value.filter((el) => el !== button));
} else {
let tmp = value;
tmp.push(button);
setValue(tmp);
}
console.log(value);
};
const buttonList = () => {
setListButtons(
BUTTONS.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={value.includes(bt.id) ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))
);
};
useEffect(() => {
buttonList();
}, [value]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>{listButtons}</div>
</div>
);
}
If you select all 3 buttons then select 1 more button the css will change.
I am trying to use these as buttons as toggle switches.
I have an example running #
Stackblitz
Any help is much appreciated.
Thanks
I think that what you want to achieve is way simpler:
You just need to store the current ID of the selected button.
Never store an array of JSX elements inside a state. It is not how react works. Decouple, only store the info. React component is always a consequence of a pattern / data, never a source.
You only need to store the necessary information, aka the button id.
Information that doesn't belong to the state of the component should be moved outside. In this case, BUTTONS shouldn't be inside your <App>.
Working code:
import React, { useState } from 'react';
import './style.css';
const BUTTONS = [
{ id: 123, title: 'button1', selected: false },
{ id: 456, title: 'button2', selected: false },
{ id: 789, title: 'button3', selected: false },
];
export default function App() {
const [buttons, setButtons] = useState(BUTTONS);
const handleButton = (buttonId) => {
const newButtons = buttons.map((btn) => {
if (btn.id !== buttonId) return btn;
btn.selected = !btn.selected;
return btn;
});
setButtons(newButtons);
};
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>
{buttons.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={bt.selected ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))}
</div>
</div>
);
}
I hope it helps.
Edit: the BUTTONS array was modified to add a selected property. Now several buttons can be selected at the same time.

React video component keeps re-rendering on browser tab switch even if using useCallBack function

Not sure if something is wrong with this Library or not. I have a video player component. I am using Plyr video player to style and display movies from YouTube and Vimeo.
I am facing a weird issue where, whenever I switch my browser tab.. the video player re-renders and loads again and again.
I am using useCallBack to only re-run if video ID or video Platform has been changed.
import { useCallback, useEffect, useState } from "react";
import getYouTubeId from "get-youtube-id";
import Plyr from "plyr-react";
import "plyr-react/dist/plyr.css";
interface videoInterface {
value: {
href: string;
videoPlatform: string;
};
}
export const video = ({ value: { href, videoPlatform } }: videoInterface) => {
const [videoID, setVideoID] = useState();
const loadVideo = useCallback(() => {
const id = getYouTubeId(href);
setVideoID(id);
}, [videoID, videoPlatform]);
useEffect(() => {
loadVideo();
}, [href]);
return (
videoID && (
<div className="relative my-6">
<div className="overflow-hidden rounded-md">
<Plyr
source={{
type: "video",
sources: [
{
src: videoID,
provider: videoPlatform,
},
],
}}
options={{
controls: [
"play-large",
"play",
"progress",
"current-time",
"mute",
"volume",
"fullscreen",
],
}}
/>
</div>
</div>
)
);
};

React Hook Form Register Different Forms With Same Field Names

I have a material ui stepper in which there are multiple forms using react-hook-form. When I change between steps, I would expect a new useForm hook to create new register functions that would register new form fields, but when I switch between steps, the second form has the data from the first. I've seen at least one question where someone was trying to create two forms on the same page within the same component but in this case I am trying to create two unique forms within different steps using different instances of a component. It seems like react-hook-form is somehow not updating the useForm hook or is recycling the form fields added to the first register call.
Why isn't react-hook-form using a new register function to register form fields to a new useForm hook? Or at least, why isn't a new useForm hook being created between steps?
DynamicForm component. There are two of these components (one for each step in the stepper).
import { Button, Grid } from "#material-ui/core";
import React from "react";
import { useForm } from "react-hook-form";
import { buttonStyles } from "../../../styles/buttonStyles";
import AppCard from "../../shared/AppCard";
import { componentsMap } from "../../shared/form";
export const DynamicForm = (props) => {
const buttonClasses = buttonStyles();
const { defaultValues = {} } = props;
const { handleSubmit, register } = useForm({ defaultValues });
const onSubmit = (userData) => {
props.handleSubmit(userData);
};
return (
<form
id={props.formName}
name={props.formName}
onSubmit={handleSubmit((data) => onSubmit(data))}
>
<AppCard
headline={props.headline}
actionButton={
props.actionButtonText && (
<Button className={buttonClasses.outlined} type="submit">
{props.actionButtonText}
</Button>
)
}
>
<Grid container spacing={2}>
{props.formFields.map((config) => {
const FormComponent = componentsMap.get(config.component);
return (
<Grid key={`form-field-${config.config.name}`} item xs={12}>
<FormComponent {...config.config} register={register} />
</Grid>
);
})}
</Grid>
</AppCard>
</form>
);
};
N.B. The images are the same because the forms will contain the same information, i.e. the same form fields by name.
Entry for first form:
Entry for the second form:
Each form is created with a config like this:
{
component: DynamicForm,
label: "Stepper Label",
config: {
headline: "Form 1",
actionButtonText: "Next",
formName: 'form-name',
defaultValues: defaultConfigObject,
formFields: [
{
component: "AppTextInput",
config: {
label: "Field 1",
name: "field_1",
},
},
{
component: "AppTextInput",
config: {
label: "Field2",
name: "field_2",
},
},
{
component: "AppTextInput",
config: {
label: "Field3",
name: "field_3",
},
},
{
component: "AppTextInput",
config: {
label: "Field4",
name: "field4",
},
},
],
handleSubmit: (formData) => console.log(formData),
},
},
And the active component in the steps is handled like:
import { Button, createStyles, makeStyles, Theme } from "#material-ui/core";
import React, { useContext, useEffect, useState } from "react";
import { StepperContext } from "./StepperProvider";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
buttonsContainer: {
margin: theme.spacing(2),
},
buttons: {
display: "flex",
justifyContent: "space-between",
},
})
);
export const StepPanel = (props) => {
const { activeStep, steps, handleBack, handleNext, isFinalStep } = useContext(
StepperContext
);
const [activeComponent, setActiveComponent] = useState(steps[activeStep]);
const classes = useStyles();
useEffect(() => {
setActiveComponent(steps[activeStep]);
}, [activeStep]);
return (
<div>
<activeComponent.component {...activeComponent.config} />
{
isFinalStep ? (
<div className={classes.buttonsContainer}>
<div className={classes.buttons}>
<Button disabled={activeStep === 0} onClick={handleBack}>
Back
</Button>
<Button
variant="contained"
color="primary"
onClick={props.finishFunction}
>
Finish And Submit
</Button>
</div>
</div>
)
:
null
}
</div>
);
};
From your image, every form looks the same. You should try providing a unique key value to your component so React know that each form is different. In this case it can be the step number for example:
<activeComponent.component {...props} key='form-step-1'>

What is the best way to handle state/props if i want to fetch and display a separate component when a specific link is clicked?

I created a CodeSandbox so I can elaborate my question.
I would like to ask for your suggestion on my Project:
I currently have a website portfolio app that are divided into 4 pages:
Loading.js directly fetch -> Home.js
About.js
Contact.js
Work.js – it displays a link of my projects that will open a Sliding Sidebar/Side Drawer
feature.
What I wanted to do is to fetch the individual project components and pass it in the Sliding Sidebar once a specific project was clicked by the user.
My question is what is the best way to manage the state? how do I pass the props from the project that was clicked and display the specific project component from the components folder?
CodeSandbox Link <----
updated work.js
import React, { useState } from "react";
import StyledWorkNav from "./StyledWorkNav";
import SideDrawer, { StyledDrawer } from "./SideDrawer";
import Project1 from "./components/Project1";
import Project2 from "./components/Project2";
import Project3 from "./components/Project3";
const Work = () => {
const [drawerOpen, setDrawerOpen] = useState(false);
const [projects, setProjects] = useState([
{ name: 'Project 1', projId: '1', dataText: 'Proj 1', comp:"" },
{ name: 'Project 2', projId: '2', dataText: 'Proj 2', comp:"Project2" },
{ name: 'Project 3', projId: '3', dataText: 'Proj 3', comp:"Project3" },
]);
const [selectedProject, setSelectedProject] = useState(null);
const strToComponent = {
Project1: <Project1/>,
Project2: <Project2/>,
Project3: <Project3/>
}
const openDrawerHandler = () => {
if (!drawerOpen) {
setDrawerOpen(true);
} else {
setDrawerOpen(false);
}
};
const closeDrawerHandler = () => {
setDrawerOpen(false);
};
// -------------------****** update **************
let drawer;
if (drawerOpen) {
drawer = (
<SideDrawer
close={closeDrawerHandler}
sidebar={{ StyledDrawer }}
// pass down here one of the wanted component : project1.js, 2 etc..
project={
<Project1
selectedProject={selectedProject} // you can pass the selected
// project as prop for
// project1.js for example
/>
}
/>
);
}
return (
<StyledWorkNav>
<ul>
{projects.map((project) => (
<li
key={project.projId}
onClick={() => {
setSelectedProject(project);
openDrawerHandler();
}}>
<p data-text={project.dataText}>{project.name}</p>
</li>
))}
{selectedProject && drawer}
</ul>
</StyledWorkNav>
);
};
export default Work;
you can do something like this :
Upadate
imports ......
// this state will contain all your projects
const [projects, setProjects] = useState([
{
id: 1,
name: "project1"
},
{
id: 2,
name: "project2"
},
.....
])
// this will contain on of the project selected from the list of
// projects
const [selectedProject, setSelectedProject] = useState({
id: 1,
name: "project1"
})
return (
<>
<List>
{ projects.map(project => (
<ListItem key={project.id} onClick={() => setSelectedProject(project)}>
{project.name}
</ListItem>
))
}
</List>
{
selectedProject &&
<Sidebar // the selected project goes here and change every time a different project selected
project={selectedProject}
/>
}
</>
)
export ......

Resources