How to show loader in button only to current mapped value? - javascript

I have placed loader if the button is clicked the loader runs, but its mapped to get value from api so if i click one button the loader runs for all button, how to do it only for corresponding element
my loader is:
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info"> {this.state.loading ? (
<div className="search-loader">
<Loader
visible={this.state.loading}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"}</button>
As the button is mapped in loop loader runs for all button if i click one button,
eg:

You are setting state.loading to true when a button is clicked, I suppose. But in your Loader component you are using that one state.loading variable for each of the buttons and whichever button is clicked, each of the loaders receive the true value for visible.
A solution would be to store the id of the provider in state.providerIdLoading if the provider is loading and for the condition of visible in the Loader you could place state.providerIdLoading === providerList.id. When the provider is finished loading you could set the value to false or whatever is not a providerId.

I think you should change your loading state to something like
const [loading, setLoading] = useState({providerID1: false, providerID2: false, ...})
and your Loader condition will change to
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info"> {this.state.loading[providerList.id] ? (
<div className="search-loader">
<Loader
visible={this.state.loading[providerList.id]}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"}</button>

Here you go with a solution
this.state = {
loading: ""
};
onAddProvider = id => {
this.setState({ loading: id });
}
<button id="Addlist" onClick={() => this.onAddProvider(providerList.id)} className="btn info">
{
this.state.loading === providerList.id ? (
<div className="search-loader">
<Loader
visible={this.state.loading === providerList.id}
type="Oval"
color="#092b50"
height={50}
width={50}
/>
</div>
) : "Add to List"
}
</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Instead of having loading as boolean, please use loading as id and match with the current id.
Once loading is done then reset the value to empty string like
this.setState({ loading: "" });

Related

Dropdown filter using React

I am currently stuck on creating this dropdown filter functionality in React. I have this array here that I have in a solutions.json file:
[
{"name": "Employer Branding", "id": "415"},
{"name": "Account Based Marketing", "id": "414"},
{"name": "Thought Leadership", "id": "413"}
]
I want to filter out the case studies 'solutions' that I have displaying dynamically through a REST API. I currently have it showing ALL case studies. This is my code:
<div className={classes.cardContainer}>
<ul className="ulGrid">
{caseStudies.map((result) => {
return (
<li className={classes.liGrid} key={result.id}>
<LayeredCard
href={`/case-studies/${result.slug}`}
key={result.id}
title={result.title.rendered}
icon={result.acf.icon}
subtitle={result.acf.case_study_pre_heading}
readMore="Read More"
></LayeredCard>
</li>
);
})}
</ul>
</div>
I want to be able to filter these case studies through their solutions category and make it a condition that if the "solutions" ids match then render filtering result.
API data from a case study
So far I have created a dropdown filter without it actually filtering in a dropdown.js file:
import { useState, useRef, useEffect } from "react";
export default function Dropdown({
options,
prompt,
value,
onChange,
id,
label,
}) {
const [open, setOpen] = useState(false);
const ref = useRef(null);
//tracking when the dropdown is clicked
useEffect(() => {
document.addEventListener("click", close); //when click happens we call the close function
return () => document.removeEventListener("click", close);
}, []);
// (&& logical AND operator), if our dropdown is open and we click outside of the dropdown than the e.target is the
// html document which will NOT be equal to ref.current (our dropdown) which will close the dropdown as a result
function close(e) {
setOpen(e && e.target === ref.current);
}
return (
<div className={classes.dropdown}>
<div className={classes.control} onClick={() => setOpen((prev) => !prev)}>
<div className={classes.selectedValue} ref={ref}>
{/* if there is a value clicked then that shows if not the prompt shows */}
{value ? value[label] : prompt}
</div>
<div
className={`${classes.arrow} ${open ? `${classes.open}` : null} `}
></div>
<div
className={`${classes.options} ${open ? `${classes.open}` : null} `}
>
{/* className={classes.arrow open ? classes.open} */}
{options.map((option) => (
//the selected value is indicated with a css style
<div
key={option[id]}
className={`${classes.option} ${
value === option ? `${classes.selected}` : null
} `}
onClick={() => {
onChange(option); //change this
setOpen(true); //when you click the option the dropdown then closes.
}}
>
{option[label]}
</div>
))}
</div>
</div>
</div>
);
}
I am calling the Dropdown function in my casestudies.js file like so:
import solutions from "../data/solutions.json";
import Dropdown from "../components/dropdown/filterDropdown/Dropdown";
<Dropdown
options={solutions}
prompt="Solutions"
id="id"
label="name"
value={value}
onChange={(val) => setValue(val)}
/>
<div className={classes.cardContainer}>
<ul className="ulGrid">
{caseStudies.map((result) => {
return (
<li className={classes.liGrid} key={result.id}>
<LayeredCard
href={`/case-studies/${result.slug}`}
key={result.id}
title={result.title.rendered}
icon={result.acf.icon}
subtitle={result.acf.case_study_pre_heading}
readMore="Read More"
></LayeredCard>
</li>
);
})}
</ul>
</div>
Not sure how to render the filtering result. Can anyone help?

My onClose or onOpen prop functionality is not working

I am trying to change and wrap another component "Modal" to my conversations panel.
My last component's name was SlidingPanel which had a different functionality. I am trying to pass my onClickOutside, open and or onCloseClick prop to the modal so that when clicking the close button or clicking outside the modal, the modal closes and opens again. Somehow I pass the props and they are not working.
I have a ChatModuleContainer which returns my ChatModule component:
const onOpenChat = () => {
if (props.onOpenChatModal) props.onOpenChatModal();
};
return (
<ChatModule
fetching={!ready}
notifications={notifications}
unreadMessages={unreadMessages}
panelTitle={panelTitle}
panelTitleIcon={MODAL_TITLE_ICON}
emptyMessage={emptyMessage}
top={panelTop}
bottom={panelBottom}
chatButtonTop={chatButtonTop}
chatButtonBottom={chatButtonBottom}
floatingButtonRef={floatingButtonRef}
onNavigateToItem={onNavigateToItem}
onOpenChat={onOpenChat}
{...props}
/>
);
Inside my ChatModule component I return my chatting float button that opens my ChatNotificationsPanel component:
const closePanelClicked = (e) => {
closeChatNotificationsPanel();
};
return (
<div
className={chatNotificationsPanelClassName}
style={chatNotificationsPanelContainerStyle}
>
{!showCloseButton && renderFloatingChatButton()}
<ChatNotificationsPanel
fetching={fetching}
open={chatNotificationsPanelOpen}
isMobile={isMobileWidth}
notifications={notifications}
panelTitle={panelTitle}
panelTitleIcon={panelTitleIcon}
emptyMessage={emptyMessage}
onNavigateToItem={notificationClicked}
onCloseClick={closePanelClicked}
onClickOutside={closePanelClicked}
width={chatNotificationsPanelWidth}
/>
{showCloseButton && renderFloatingButtons()}
</div>
);
in my ChatNotificationsPanel is where I want to change my SlidingPanel component to my Modal component.
This is how it looks now:
return (
<SlidingPanel
width={width}
open={open}
top={top}
bottom={bottom}
onClickOutside={onClickOutside}
side="right"
>
<div className={listClassName}>
<CuaddsWindow
title={panelTitle}
icon={panelTitleIcon}
showCloseButton={true}
onClose={onCloseClick}
className={usesHelpCenter ? "chatContainerStyle" : "oldChatStyle"}
classNameToAdd={"titleStyle"}
headerBackgroundColor={usesHelpCenter ? "#474747" : ""}
></CuaddsWindow>
{this.renderNotificationsList()}
</div>
</SlidingPanel>
);
I am trying to Change SlidingPanel to Modal, like this:
return (
<Modal
onClickOutside={onClickOutside}
style={{ width: width, top: top, bottom: bottom }}
onClick={open}
>
<div className={listClassName}>
<Window
title={panelTitle}
icon={panelTitleIcon}
showCloseButton={true}
onClose={onCloseClick}
className={usesHelpCenter ? "chatContainerStyle" : "oldChatStyle"}
classNameToAdd={"titleStyle"}
headerBackgroundColor={usesHelpCenter ? "#474747" : ""}
></Window>
{this.renderNotificationsList()}
</div>
</Modal>
);
But it is not working, Can anyone help me to see what I am doing wrong? and how should I fix this?

Expand/Collapse all data

I am making a Accordion and when we click each individual item then its opening or closing well.
Now I have implemented expand all or collapse all option to that to make all the accordions expand/collapse.
Accordion.js
const accordionArray = [
{ heading: "Heading 1", text: "Text for Heading 1" },
{ heading: "Heading 2", text: "Text for Heading 2" },
{ heading: "Heading 3", text: "Text for Heading 3" }
];
.
.
.
{accordionArray.map((item, index) => (
<div key={index}>
<Accordion>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text expandAll={expandAll}>
<p className="text">{item.text}</p>
</Text>
</Accordion>
</div>
))}
And text.js is a file where I am making the action to open any particular content of the accordion and the code as follows,
import React from "react";
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
{this.props.expandAll ? (
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
) : (
<div className={`content ${this.props.text ? "open" : ""}`}>
{this.props.text ? this.props.children : ""}
{this.props.text
? this.props.render && this.props.render(this.props.text)
: ""}
</div>
)}
</div>
);
}
}
export default Text;
Here via this.props.expandAll I am getting the value whether the expandAll is true or false. If it is true then all accordion will get the class className={`content open`} so all will gets opened.
Problem:
The open class is applied but the inside text content is not rendered.
So this line doesn't work,
{this.props.render && this.props.render(this.props.text)}
Requirement:
If expand all/collapse all button is clicked then all the accordions should gets opened/closed respectively.
This should work irrespective of previously opened/closed accordion.. So if Expand all then it should open all the accordion or else needs to close all accordion even though it was opened/closed previously.
Links:
This is the link of the file https://codesandbox.io/s/react-accordion-forked-sm5fw?file=/src/GetAccordion.js where the props are actually gets passed down.
Edit:
If I use {this.props.children} then every accordion gets opened.. No issues.
But if I open any accordion manually on click over particular item then If i click expand all then its expanded(expected) but If I click back Collapse all option then not all the accordions are closed.. The ones which we opened previously are still in open state.. But expected behavior here is that everything should gets closed.
In your file text.js
at line number 9. please replace the previous code by:
{this.props.children}
Tried in the sandbox and worked for me.
///
cant add a comment so editing the answer itself.
Accordian.js contains your hook expandAll and the heading boolean is already happening GetAccordian.js.
I suggest moving the expand all to GetAccordian.js so that you can control both values.
in this case this.props.render is not a function and this.props.text is undefined, try replacing this line
<div className={`content open`}>
{this.props.render && this.props.render(this.props.text)}
</div>
by this:
<div className={`content open`}>
{this.props.children}
</div>
EDIT: //
Other solution is to pass the expandAll property to the Accordion component
<Accordion expandAll={expandAll}>
<Heading>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text>
<p className="text">{item.text}</p>
</Text>
</Accordion>
then in getAccordion.js
onShow = (i) => {
this.setState({
active: this.props.expandAll ? -1: i,
reserve: this.props.expandAll ? -1: i
});
if (this.state.reserve === i) {
this.setState({
active: -1,
reserve: -1
});
}
};
render() {
const children = React.Children.map(this.props.children, (child, i) => {
return React.cloneElement(child, {
heading: this.props.expandAll || this.state.active === i,
text: this.props.expandAll || this.state.active + stage === i,
onShow: () => this.onShow(i)
});
});
return <div className="accordion">{children}</div>;
}
};
Building off of #lissettdm answer, it's not clear to me why getAccordion and accordion are two separate entities. You might have a very valid reason for the separation, but the fact that the two components' states are interdependent hints that they might be better implemented as one component.
Accordion now controls the state of it's children directly, as before, but without using getAccordion. Toggling expandAll now resets the states of the individual items as well.
const NormalAccordion = () => {
const accordionArray = [ //... your data ];
const [state, setState] = useState({
expandAll: false,
...accordionArray.map(item => false),
});
const handleExpandAll = () => {
setState((prevState) => ({
expandAll: !prevState.expandAll,
...accordionArray.map(item => !prevState.expandAll),
}));
};
const handleTextExpand = (id) => {
setState((prevState) => ({
...prevState,
[id]: !prevState[id]
}));
};
return (
<>
<div className="w-full text-right">
<button onClick={handleExpandAll}>
{state.expandAll ? `Collapse All` : `Expand All`}
</button>
</div>
<br />
{accordionArray.map((item, index) => (
<div key={index}>
<div className="accordion">
<Heading handleTextExpand={handleTextExpand} id={index}>
<div className="heading-box">
<h1 className="heading">{item.heading}</h1>
</div>
</Heading>
<Text shouldExpand={state[index]}>
<p className="text">{item.text}</p>
</Text>
</div>
</div>
))}
</>
);
};
Heading passes back the index so the parent component knows which item to turn off.
class Heading extends React.Component {
handleExpand = () => {
this.props.handleTextExpand(this.props.id);
};
render() {
return (
<div
style={ //... your styles}
onClick={this.handleExpand}
>
{this.props.children}
</div>
);
}
}
Text only cares about one prop to determine if it should display the expand content.
class Text extends React.Component {
render() {
return (
<div style={{ ...this.props.style }}>
<div
className={`content ${this.props.shouldExpand ? "open" : ""}`}
>
{this.props.shouldExpand ? this.props.children : ""}
</div>
</div>
);
}
}

Cancel Button handle previous state

I have a save button that saves the checkbox and the number field, then makes a call to update those numbers. I am adding a 'Cancel' button that I want to be able to use to revert the status to the previous state (prior to saving). what is the best way to do this in React?
This is my code
class Alerts extends Component {
state = {
prevId: null,
checked: this.props.preferences.last_order_alert_enabled,
days: this.props.preferences.last_order_alert
};
static getDerivedStateFromProps(props, state) {
// Store prevId in state so we can compare when props change.
// Clear out previously-loaded data (so we don't render stale stuff).
if (props.dealerID !== state.prevId) {
//update action goes here...
//props.actions.getUsers(props.dealerID);
return {
prevId: props.dealerID
};
}
// No state update necessary
return null;
}
componentDidMount = () => {
console.log('mountDays',this.props.preferences.last_order_alert);
console.log('mountCheck',this.props.preferences.last_order_alert_enabled);
this.setState({ days: this.props.preferences.last_order_alert });
this.setState( {checked: this.props.preferences.last_order_alert_enabled})
};
toggleAlerts = e => {
console.log('lastOrderEnable', this.props.preferences.last_order_alert_enabled);
console.log('lastOrderDaysAlert', this.props.preferences.last_order_alert);
this.props.actions.updateLastOrderAlert(
this.props.preferences.id,
this.props.dealerID,
this.props.isGroup,
this.props.preferences.last_order_alert = this.state.checked,
this.props.preferences.last_order_alert_enabled = this.state.days
);
};
handleCheck = event => {
console.log('called', {checked: Number(event.target.checked) });
this.setState({checked: Number(event.target.checked) })
};
handleChange = e => {
console.log('days', {days: e.target.value});
this.setState({days: e.target.value})
};
render = () => {
return (
<div className="preferenceContainer">
<div className="preferenceRow lg">
<div className="preferenceLabelWrapper">
<div className="preferenceLabel">Enable Alert</div>
<div className="preferenceSubLabel">
Toggles the Last Order Alert Email
</div>
</div>
<div className="preferenceInput">
<input
type="checkbox"
checked={this.state.checked}
onChange={this.handleCheck.bind(this)}
/>
</div>
</div>
<div className="preferenceRow">
<div className="preferenceLabelWrapper">
<div className="preferenceLabel">Days Since Last Order</div>
</div>
<div className="preferenceInput">
<input
type="number"
value={this.state.days}
// onBlur={this.handleAlertDays}
onChange={this.handleChange.bind(this)}
style={{ width: "50px" }}
/>
</div>
</div>
<div className="preferenceRow" style={{ display: "flex" }}>
<button
className={'btn btn-default'}
type="submit"
onClick={this.componentDidMount}
>Cancel
</button>
<button
style={{ marginLeft: "auto" }}
className={'btn btn-primary'}
type="submit"
onClick={this.toggleAlerts}
>Save
</button>
</div>
</div>
);
};
}
Right now is currently saving the changes and rendering them back when I hit the cancel button(prior to hitting save). However, after hitting save and going to another page and then coming back, it does not render with the initial state in componentDidMount.
Never use lifecycle methods in onClick
If I understood correctly, you should use componentDidUpdate(prevProps, prevState) lifecycle method to get previous props and previous state after change them. Every time after redrawing react will give you previous props and previous state in that method.
Sorry, if this is not what you need

ReactJs adding active class to button

I have five buttons, dynamically created. My target is: when any button is clicked to add active class to it, and of course if any other has that active class to remove it. How can I achieve that?
<div>
{buttons.map(function (name, index) {
return <input type="button" value={name} onClick={someFunct} key={ name }/>;
})}
</div>
You need to introduce state to your component and set it in onClick event handler. For example output of render method:
<div>
{buttons.map(function (name, index) {
return <input
type="button"
className={this.state.active === name ? 'active' : ''}
value={name}
onClick={() => this.someFunct(name)}
key={ name } />;
})}
</div>
event handler (element method):
someFunct(name) {
this.setState({ active: name })
}
One of the easiest way to add active class is setting state and changing that state on each switch, by the state value you can change the active class of the item.
I also had an same issue with switching the active class in list.
Example:
var Tags = React.createClass({
getInitialState: function(){
return {
selected:''
}
},
setFilter: function(filter) {
this.setState({selected : filter})
this.props.onChangeFilter(filter);
},
isActive:function(value){
return 'btn '+((value===this.state.selected) ?'active':'default');
},
render: function() {
return <div className="tags">
<button className={this.isActive('')} onClick={this.setFilter.bind(this, '')}>All</button>
<button className={this.isActive('male')} onClick={this.setFilter.bind(this, 'male')}>male</button>
<button className={this.isActive('female')} onClick={this.setFilter.bind(this, 'female')}>female</button>
<button className={this.isActive('child')} onClick={this.setFilter.bind(this, 'child')}>child</button>
<button className={this.isActive('blonde')} onClick={this.setFilter.bind(this, 'blonde')}>blonde</button>
</div>
}
});
hope this will help you!
One of the easiest solution for adding active class to the current button (highlight it) for react developers.
const {useState,Fragment} = React;
const App = () => {
const [active, setActive] = useState("");
const handleClick = (event) => {
setActive(event.target.id);
}
return (
<Fragment>
<button
key={1}
className={active === "1" ? "active" : undefined}
id={"1"}
onClick={handleClick}
>
Solution
</button>
<button
key={2}
className={active === "2" ? "active" : undefined}
id={"2"}
onClick={handleClick}
>
By
</button>
<button
key={3}
className={active === "3" ? "active" : undefined}
id={"3"}
onClick={handleClick}
>
Jamal
</button>
</Fragment>
);
}
ReactDOM.render(
<App/>,
document.getElementById("react")
);
.active{
background-color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Categories

Resources