I created a wrapper component in react for buttons on my app that adds a little svg loading spinner to a button when things are loading on my map to give a little feedback to the user. I am noticing on using mobile safari/chrome that the svg spinner tends to freeze up when I am firing multiple service calls, and am trying to figure out how to smooth this out.
So my button looks like so -
constructor(props) {
super(props);
this.checkIfDisabled = this.checkIfDisabled.bind(this);
this.renderButtonInner = this.renderButtonInner.bind(this);
}
checkIfDisabled() {
if (this.props.isLoading) {
return true;
} else if (this.props.disabled === true) {
return true;
}
return false;
}
renderButtonInner() {
if (this.props.isLoading) {
return (
<div>
<span className="button-loader"></span> {this.props.children}
</div>
);
}
return this.props.children;
}
render() {
return (
<button
className={`LoadingButton ${this.props.styleClasses}`}
onClick={this.props.onClick}
disabled={this.checkIfDisabled()}
type={this.props.type || 'button' }
>
{this.renderButtonInner()}
</button>
);
}
and the accompanying css (scss) :
.LoadingButton {
.button-loader {
background: url(/images/icons/loading-button.svg) center center no-repeat;
background-size: contain;
height: 20px;
width: 20px;
float: left;
}
}
And I have been using this component like so -
<LoadingButton
isLoading={bool to tell if button is in loading state}
styleClasses="my custom classes here"
onClick={click callback here}
>
Button!
</LoadingButton>
It has been working great for desktop, but I am noticing on mobile that when async calls are fired to my backend, the spinner freezes up. I am not sure what else I should do to resolve this - maybe I shouldn't be using an svg as a background image? IS there a better approach to adding this spinner that might stop it from freezing? Thanks for reading!
Related
I'm developing a Svelte UI with full page navigation using svelte-spa-router.
I'm facing the case where the UX designer defined the transition between page "per transition" and not per page has it's meant to be in Svelte (AFAIK).
E.g. in this UX page B1 out transition would :
disappeared instantly when going back to the home page A ;
slide left when going to C1 ;
dissolve when going to B2.
The UX actually makes sense because B1/B2, C1/C2 are similar but treats the same subject from a different point of view.
Svelte transition are working great but are defined per component, with a in transition and an out transition.
I tried leveraging the fact that transition property could be object and reactive.
<script>
import { fade } from "svelte/transition";
let page = "A";
let duration = 0;
function goto(dest) {
if(dest == "A" || page == "A") {
duration = 0;
} else {
duration = 400;
}
page = dest;
}
</script>
{#if page == "A"}
<section class="A" transition:fade={{duration: duration}}>
<h1>Page A</h1>
<nav on:click={e => goto("B1")}>Goto B1</nav>
<nav on:click={e => goto("B2")}>Goto B2</nav>
</section>
{:else if page == "B1"}
<section class="B1" transition:fade={{duration: duration}}>
<h1>Page B1</h1>
<nav on:click={e => goto("A")}>Goto Back</nav>
<nav on:click={e => goto("B2")}>Goto B2</nav>
</section>
{:else if page == "B2"}
<section class="B2" transition:fade={{duration: duration}}>
<h1>Page B2</h1>
<nav on:click={e => goto("A")}>Goto Back</nav>
<nav on:click={e => goto("B1")}>Goto B1</nav>
</section>
{/if}
<style>
section {
position: absolute;
width: 500px;
height: 500px;
}
section.A {
background: pink;
}
section.B1 {
background: blue;
}
section.B2 {
background: yellow;
}
</style>
But I cannot figure out how to change the transition effect (maybe a custom transition function ?).
Moreover, this solution seems very complicated, time consuming and could really turn to a ball of spaghetti in a more complex UX.
In addition, in svelte-spa-router I did not found a way to know where I'm coming from (i.e. the prevision location) to manage the transition accordingly.
Any thoughts ?
The in: syntax accepts javascript functions, things like fade are regular javascript functions this allows for switching.
// pageTransion.js
function slidePage(el) {
return fly(el, { x: 200, duration: 300 });
}
function disolvePage(el) {
return fade(el, { duration: 300 });
}
let previous = "";
let current = "";
export function setNextPage(next) {
previous = current;
current = next;
}
export function pageTransition(el) {
const transitions = {
"b1-b2": disolvePage,
"b1-c1": slidePage,
// etc
};
return transitions[previous + "-" + current];
}
// b1.svelte
<div transition:pageTransition>
You'll need to call the setNextPage before activating the page.
svelte-spa-router doesn't expose the previous url, but you could listen to the onhashchange event and store the url history yourself.
( ordering is important, you can use the capture phase to run before the the spa-router code.)
I am creating a list of divs, which was created with map.
function renderButtons(){
const options = [...Array(10).keys()] // returns [0,1,2...9]
return _.map(options, (option)=> renderOption(option))
}
function renderOption(option:number){
return (
<div className="option-container" onClick={() => setLowerContainerVisible(true)}>
<img alt="" src={"./images/feedback-icons/icon-"+option.toString()+".svg"}/>
{option+1}
</div>
)
}
this renders a list of divs, and I was able to change each div background, when hover, like this:
.option-container{
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container:hover{
background-color: #adadf3;
}
I wish to be able to click on a div, and change its background color to white. everything I try will change the background of all the 10 divs to white. How can I make it so only the clicked one is changed?
I suggest that you use renderOption and renderButtons as two components rather than plain functions. In the RenderButtons component, you can use some state to maintain which item is clicked, and within RenderOption you can control whether the background color is white or not based on wehther or not the current rendered button is the clicked option. In your .map() method, you can use component rather than a function call <RenderOption option={option} ... />.
See example below:
const {useState} = React;
function RenderButtons() {
const [clickedItem, setClickedItem] = useState(-1);
return Array.from(
Array(10).keys(),
option => <RenderOption isClicked={clickedItem === option} option={option} setClicked={setClickedItem}/>
);
}
function RenderOption({isClicked, option, setClicked}) {
const handleClick = () => {
// setLowerContainerVisible(true) / other code to run when you click
setClicked(option); // set to current option
}
return (
<div className={"option-container " + (isClicked ? "clicked" : "")} onClick={handleClick}>
{option+1}
</div>
)
}
ReactDOM.render(<RenderButtons />, document.body);
.option-container {
width: 76px;
height: 100px;
background-color: #7777ff;
display: flex;
}
.option-container.clicked, .option-container:hover {
background-color: #adadf3;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
The className is a little messy as it involves a ternary, to clean this up it might be worth looking into using a node package such as classnames which allows you to easily build a list of classes based on conditions.
Do it in the event listener function:
<div className="option-container" onClick={highlightAndsetLowerContainerVisible}>
function highlightAndsetLowerContainerVisible(event){
event.preventDefault();
setLowerContainerVisible(true)
event.currentTarget.style.backgroundColor = "red";
}
You then might also want to reset the background color of the other divs
i am trying to create a popupdialog to be shown when user clicks on a button. for that i am using portal.
i want it to look like in the picture below,
So basically, when user clicks on the add button i want the popup dialog to display like in the picture above.
in the popup component i want to render overlay with children. and when user clicks on overlay div the popup should close.
I have something that kind of works without using Portal and is like below,
below is my code that is without using Portal,
function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
const [isClicked, setIsClicked] = React.useState(false);
const handleButtonClick = () => {
if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
if (setSomething) setSomething(isDialogOpen);
setIsClicked(!isClicked);
};
return (
<button onClick={handleButtonClick}>click</button>
{isDialogOpen && isClicked &&
<Overlay>
<Dialog>
//some divs
</Dialog>
</Overlay>
}
);
}
const Overlay = styled.div`
position: fixed;
padding-top:60px;
bottom: 40px;
top: 0;
left: 0;
right: 0;
backdrop-filter: blur(8px);
z-index: 100;
display: flex;
justify-content: center;
align-items: center;
`;
const Dialog = styled.div`
padding: 16px;
width: 384px;
max-height: calc(100% - 200px);
display: flex;
flex-direction: column;
`;
Now i am rewriting above using portal like below,
function Parent({isDialogOpen, setDialogOpen, setSomething}: Props) {
const [isClicked, setIsClicked] = React.useState(false);
const handleButtonClick = () => {
if (setIsDialogOpen) setIsDialogOpen(!isDialogOpen);
if (setSomething) setSomething(isDialogOpen);
setIsClicked(!isClicked);
};
return (
<button onClick={handleButtonClick}>click</button>
{isDialogOpen && isClicked &&
<Popup setSomething={setSomething} setIsDialogOpen={setIsDialogOpen} setIsClicked=
{setIsClicked}>
<Dialog>
//some divs
</Dialog>
</Overlay>
}
);
}
function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {
return ReactDom.createPortal(
<>
<Overlay
onClick={() => {
if (setIsDialogOpen) setIsDialogOpen(false);
if (setSomething) setSomething(true);
setIsClicked(false);
}}
>
{children}
</Overlay>
</>,
//dont know what to pass here
);
}
Basically as seen in picture above, i want to render the overlay with dialog.
now in popup component i want to create div with classname 'popup' and find the div element with class navbar and attach this div popup to the navbar div
and pass this div element with class popup in the reactDOM.createPortal.
i am new to react and not sure how to do this. could someone help me with this.
thanks.
As I mentioned to you in the comment, react doesn't create the dom node for you. You must do it yourself. How you do it depends on your needs. the most basic example i can think of is below:
When component mounts first time, we need to create the portal and insert it into document.body
once we are sure portal exists, we can render into the portal using our dom ref.
function Popup({setIsClicked, setSomething, setIsDialogOpen, children}: Props) {
const [portal,setPortal] = React.useState<HTMLDivElement|null>( (document.getElementById('my-portal') as HTMLDivElement)||null);
const createPortalIfNotExists = React.useCallback(()=>{
if(portal===null){
const el = document.createElement('div');
el.id='my-portal';
document.body.appendChild(el);
// switch this line for the one above if you want it to be first in tree
// document.body.insertBefore(el, document.body.firstChild);
setPortal(document.getElementById('my-portal') as HTMLDivElement);
}
},[portal]);
createPortalIfNotExists();
if(portal===null){
return null;
}
return ReactDom.createPortal(
<>
<Overlay
onClick={() => {
if (setIsDialogOpen) setIsDialogOpen(false);
if (setSomething) setSomething(true);
setIsClicked(false);
}}
>
{children}
</Overlay>
</>,
portal
);
}
This is just one possible way of doing it. There are other more advanced use cases where you would have the portal be rendered by some other element in your component tree. But this should be enough to get you started. Also, i haven't tested this as i don't have tsc/tslint on this machine so YMMV
So, my problem is that I have a component, I associated an animation to it and it is working when the component is rendered for the first time, but on an event click I change some conditions and some props associated to this component, But my element is not re rendered, it is just changing what has been changed, that means that the element is not removed from the dom et added to the DOM again, that's why I am not able to see the animation again, so it is not re-rendered or I just did not get what re render means.
I tried some solutions of course, but I am stuck, I tried to use this method :
this.forceUpdate();
But again, I am still not getting anything.
I dont think I have to write the whole code I wrote, becuase it is a lot and includes many other things but This is what I think is needed.
methodWillReceiveProps in my component :
componentWillReceiveProps(props) {
if (props.isRerendered) {
this.forceUpdate();
}
}
props.isRendered is returning true everytime, I checked with some console.log methods.
This is what is rendered :
render() {
return (
<div
className={cs({
"tls-forms": true,
"tls-forms--large": this.props.type === "S",
"tls-forms--medium tls-forms--login": !(this.props.type === "S")
})}
>
// content here
</div>);
}
And here is the sass file and the simple fading animation :
.tls-forms {
animation: formFading 3s;
// childs properties here
}
#keyframes formFading {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
I will really appreciate any help given.
You could make use of keys that react is using to determine whether something has changed. This means that your render method should look something like this:
import shortid from "shortid";
getRandomKey = () => {
return shortid.generate();
}
render() {
return (
<div
key={this.getRandomKey()}
className={cs({
"tls-forms": true,
"tls-forms--large": this.props.type === "S",
"tls-forms--medium tls-forms--login": !(this.props.type === "S")
})}
>
// content here
</div>);
}
Since you need to run animation on each render, you'll need to generate some random key every time (that's why we are calling this.getRandomKey() on each render). You can use whatever you like for your getRandomKey implementation, though shortid is pretty good for generating unique keys.
One way of animating a component is to attach a CSS class to it. But, when animation is done, you have to detach the CSS class so that you can re-attach when you want to animate again.
Here is a basic example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
animateFlag: false
};
}
componentDidUpdate() {
if (this.state.animateFlag) {
setTimeout(() => {
this.setState({ animateFlag: false });
}, 3000);
}
}
render() {
return (
<div className="App">
<button
onClick={() =>
this.setState({ animateFlag: !this.state.animateFlag })
}
>
{this.state.animateFlag ? "Wait" : "Re-animate"}
</button>
<div className={this.state.animateFlag ? "text animate" : "text"}>
Hello CodeSandbox
</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.text {
font-size: 40px;
}
.text.animate {
animation: formFading 3s;
}
#keyframes formFading {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
<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>
<div id="root"></div>
Note that, I am setting animateFlag to false in ComponentDidUpdate, so that when I click the Re-animate button again, I can re-attach the animate class to the div element.
I set timeout duration to 3000ms because, the animation takes 3000ms.
I am currently working on my first ever react project.
I have placed an onClick event to one of the elements. This element happens to be a button. What I want to achieve is an image going from opacity to 0 in a transition to confirm the user has successfully added an input. This is set-up with the keyframe below
#showTick {
width: 30%;
opacity: 0;
}
.activateKF {
animation: showTick 0.7s;
}
#keyframes showTick {
0% {opacity: 0;}
25% {opacity: 0.5;}
50% {opacity: 1;}
75% {opacity: 0.5;}
100% {opacity: 0;}
}
The showtick styling is what the elements default style is. When the user clicks on the button, I want to add the .activateKF class to the #showTick element. I am doing this with the following code.
goalCreation=()=>{
document.getElementById("showTick").classList.remove("activateKF");
let goal = document.getElementById("enterGoal").value;
if (goal.length < 1){
return false;
} else {
document.getElementById("showTick").classList.add("activateKF");
this.props.submitGoal(goal);
}
}
I am trying to remove the class within the same function so that whenever the user clicks on it, the keyframe can once again be added to the element upon the click event - and the animation can take place. However, what I am finding is that it only works the first time.
Even if I take out the line where the class is removed, it still only works the first time. I can not figure out why?
Please can someone help, so that whenever the user clicks on the button, the keyframe becomes active everytime?
Update: I have included what this actual react component looks like as part of my code
import React, { Component } from 'react';
import '../Styles/creategoal.css';
import specificGoal from '../Images/specificgoal.png';
import cost from '../Images/cost.png';
import tick from '../Images/greentick.jpg';
import '../Styles/creategoal.css';
import '../App.css';
export default class CreateGoal extends Component {
constructor(props){
super(props);
this.state = {
showCostDiv: false,
showSpecificDiv: false
}
}
goalCreation=()=>{
let goal = document.getElementById("enterGoal").value;
if (goal.length < 1){
return false;
} else {
document.getElementById("showTick").classList.add("activateKF");
this.props.submitGoal(goal);
}
}
closeHelp=(e)=>{
let currentClicked = e.target.tagName;
if (this.state.showCostDiv && currentClicked !== "SECTION"){
this.setState({
showCostDiv: false
})
if (this.state.showSpecificDiv && currentClicked !== "SECTION"){
this.setState({
showSpecificDiv: false
})
}
}
}
openSpecificWindow=()=>{
this.setState({
showSpecificDiv: true
})
}
closeSpecificWindow=()=>{
this.setState({
showSpecificDiv: false
})
}
openCostWindow=()=>{
this.setState({
showCostDiv: true
})
}
closeCostWindow=()=>{
this.setState({
showCostDiv: false
})
}
render(){
let specificDivStatus = "hideContent";
let costDivStatus = "hideContent";
if (this.state.showSpecificDiv){
specificDivStatus = "showContent";
}
if (this.state.showCostDiv){
costDivStatus = "showContent";
}
return (
<div onClick={this.closeHelp} className="createGoal">
<div id="banner" className="goalSetBanner">
<h1>SET YOUR GOAL</h1>
</div>
<span className="goalTip">Consider the following when setting your goal:</span>
<section id="BeSpecificHelp" className={specificDivStatus}>
<p>Describe exactly what your goal is, and when its possible use numbers to make it measurable. This excercise will turn your idea or dream
even closer to reality.</p>
<br/>
<p>Examples:</p>
<p><span className="incorrect">Wrong:</span> Weight loss.<br/>
<span className="correct">Right:</span> Losing 8Kg.</p>
<p><span className="incorrect">Wrong:</span> Read more books.<br/>
<span className="correct">Right:</span> Read a new book every 15 days.</p>
<p><span className="incorrect">Wrong:</span> Buying a house.<br/>
<span className="correct">Right:</span> Buying a house within two bedrooms in a given address.</p>
<span id="closeWindowSpecific" onClick={this.closeSpecificWindow}>Close</span>
</section>
<section id="considerCostHelp" className={costDivStatus}>
<p>Do not focus only on the result you will get.</p>
<p><strong>Your time and energy are limited resources</strong></p>
<p>Reflect on what it will take you to achieve this goal.</p>
<p>Finish completing it if you are willing to pay the price.</p>
<span id="closeWindowCost" onClick={this.closeCostWindow}>Close</span>
</section>
<main className="setGoalInfo">
<div id="beSpecificGoal" className="considerGoal">
<img src={specificGoal} alt="Specific Goal" />
<span className="goalHelp">Be as specific as possible</span>
<span id="beSpecificLink" onClick={this.openSpecificWindow} className="link-span">TAP FOR MORE INFO</span>
</div>
<div id="considerCost" className="considerGoal">
<img src={cost} alt="Cost of Goal" />
<span className="goalHelp">What will it cost you?</span>
<span id="considerCost" onClick={this.openCostWindow} className="link-span">TAP FOR MORE INFO</span>
</div>
</main>
<div id="goalAdded">
<img src={tick} id="showTick" alt="Goal Added" />
</div>
<div className="inputDiv">
<input type="text" id="enterGoal" placeholder="What is your goal?"></input>
</div>
<button onClick={this.goalCreation} id="createGoal">CREATE MY GOAL</button>
</div>
)
}
}
Many thanks for the help.
Ground rule with React is that you do not manipulate the DOM directly. React will build a virtual DOM upon rendering and replace only the pieces of the DOM that it detected have changed. If you manipulate the DOM outside the React render cycle, it might not work as you intended.
Neither is it a good idea to use the id attribute on react components. For one, it reduces the re-usability of your components (id's should be unique across a page), and react will also render its own ids in the DOM.
In React, you can use the ref statement which is a function containing either null(upon unmounting) or an element after the item was mounted, however, this one is probably not what you need here (one would rather use that when you read the value from an input).
Probably, you just want to use something like React animation or you just want to add a class depending on a local component state.
From seeing your current monolithic code, you can see that you haven't worked with react all that often yet. You have lots of hard coded data, and lots of repeating concepts.
A way to achieve your current goal, would be to implement something like the following:
const { classNames } = window;
const { Component } = React;
class CheckableButton extends Component {
constructor() {
super();
this.state = {
submitted: false
};
this.handleSubmit = this.handleSubmit.bind( this );
}
componentDidUpdate() {
const { submitted } = this.state;
if (submitted) {
// trigger submitted to be cleared
this.resetTimer = setTimeout( () => this.setState( { submitted: false } ), 700 );
}
}
componentWillUnmount() {
// make sure the state doesn't get manipulated when the component got unmounted
clearTimeout( this.resetTimer );
}
handleSubmit() {
// set the submitted state to true
this.setState( { submitted: true } );
}
render() {
const { submitted } = this.state;
const { title } = this.props;
return (
<button
type="button"
className={ classNames( 'checkable', { 'checked': submitted } ) }
onClick={ this.handleSubmit }>{ title }</button>
);
}
}
ReactDOM.render(
<CheckableButton title="Create goal" />, document.getElementById('container') );
button.checkable {
padding-left: 5px;
padding-top: 5px;
padding-bottom: 5px;
padding-right: 5px;
}
.checkable::before {
display: inline-block;
width: 20px;
content: ' ';
padding-right: 5px;
}
.checkable.checked::before {
content: '✓';
color: darkgreen;
padding-right: 5px;
font-weight: bold;
opacity: 0;
animation: showTick 0.7s;
}
#keyframes showTick {
0% {opacity: 0;}
25% {opacity: 0.5;}
50% {opacity: 1;}
75% {opacity: 0.5;}
100% {opacity: 0;}
}
<script id="react" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.2/react.js"></script>
<script id="react-dom" src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/15.6.2/react-dom.js"></script>
<script id="classnames" src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.5/index.js"></script>
<div id="container"></div>
The logic you see in the component, is mainly based on react life cycle events. When the button gets clicked, the state is changed to submitted, this in turn will trigger the componentDidUpdate and there you would be able to check if the submitted flag was set to true. When it did, you can create a callback over setTimeout to remove the submitted flag again.
The handleSubmit function could of course be manipulated to call an eventhandler that was passed down through props
When you redesign your current component, you should probably think about creating components for your "windows", so that they can be manipulated through state / props as well, so they become reusable components