Svelte transition defined per transition and not per component - javascript

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.)

Related

Why does my navbar with a position of "fixed" appear , disappear and appear again? (more explanation in the description)

So I'm making a profile website and I've come across a issue.
So, I'm making some simple loading animations with React useState, setTimeout() and dynamic styling.
For example, If I want to show a loading page first (with a logo appearing and disappearing) and then show my main page for my portfolio, I would do something like this:
const [pageLoading, setPageLoading] = useState(true)
useEffect(() => {
setTimeout(() => {
setPageLoading(false)
}, 4500)
}, [])
// Icon load while page is loading
const [iconLoading, setIconLoading] = useState(false)
useEffect(() => {
setTimeout(() => {
setIconLoading(true)
}, 500);
setTimeout(() => {
setIconLoading(false)
}, 3000)
}, [])
return (
{
pageLoading
?
<div>
// Loading page content
</div>
:
<div>
// This is where main content will go, with all of the react components
<Header />
<Body />
<div>
)
So, when the page first loads, it will show a loading page with a logo image loading (appear -> disappear). I didn't add the css but it will change the opacity from 0% -> 100% with a transition of ?ms. After this, it will load a blank page with the Header and Body component appearing one by one. (Same logic applied to these components as with the ones above). However, the problem occurs when I apply the style of position fixed on my Header component.
When it's the Header component's turn to appear, it appears alright, but it suddenly disappears and appears again. If I don't use position fixed, this doesn't happens. It will only appear and that's it. Here's a sample code of the Header and Body components.
Here's also a link to a video that will show the problem in action: Video link (You will see that the Header component appears, then disappear again. I only want the Header component to appear and that's it)
// Header.jsx
const [headerLoading, setHeaderLoading] = useState(false)
useEffect(() => {
setTimeout(() => {
setHeaderLoading(true)
}, 500)
}, [])
return (
<div className={headerLoading ? "header-loading headerLoaded" : "header-loading"}>
...
.header-loading {
height: 10vh;
width: 100%;
position: fixed;
top: 0;
right: 0;
left: 0;
background-color: #062C30;
opacity: 0;
}

Pagination works but doesn't reattach javascript / css class on reload

Im building a new personal blog and I'm using ajax to post back to a C# Controller to get the results for pagination.
Page 2 loads with the results however, none of the javascript is reloaded because, I believe, when I partially reload the pagination part of the page, it destroys everything in the DOM and because the full page doesn't reload, the javascript isn't invoked.
So I'm looking for a bit of help on working out how to get the external javascript to run again. What it does is adds css classes, gives some fade effects etc.
success: function (data) {
if (data != null) {
var page = data
$('#blogsContainer').empty();
$('#blogsContainer').replaceWith(page);
So the success works, I clear out the blogsContainer with the new data.
I'm guessing I need to add a function after the replace to then apply everything that is in an external main.js file.
The main.js file looks like this
(function($) {
var contentWayPoint = function() {
var i = 0;
$('.ftco-animate').waypoint( function( direction ) {
if( direction === 'down' && !$(this.element).hasClass('ftco-animated') ) {
i++;
$(this.element).addClass('item-animate');
setTimeout(function(){
$('body .ftco-animate.item-animate').each(function(k){
var el = $(this);
setTimeout( function () {
var effect = el.data('animate-effect');
if ( effect === 'fadeIn') {
el.addClass('fadeIn ftco-animated');
} else if ( effect === 'fadeInLeft') {
el.addClass('fadeInLeft ftco-animated');
} else if ( effect === 'fadeInRight') {
el.addClass('fadeInRight ftco-animated');
} else {
el.addClass('fadeInUp ftco-animated');
}
el.removeClass('item-animate');
}, k * 50, 'easeInOutExpo' );
});
}, 100);
}
} , { offset: '95%' } );
};
contentWayPoint();
}
The first page has the following applied to it on page load:
<div class="col-md-4 d-flex ftco-animate fadeInUp ftco-animated">
<div class="blog-entry justify-content-end">
...
</div>
</div>
But as you can see, when I press page 2, the div is missing some key css
<div class="col-md-4 d-flex ftco-animate">
<div class="blog-entry justify-content-end">
</div>
</div>
How would I apply the missing css after the partial reload with ajax?
I hope this is clear what I am trying to do but if not, please just ask.
I think the solution may be to re-execute the contentWayPoint() function at the end of the success callback. However, its likely out of scope by then. There are two simple ways to ensure its not :
The cleanest would be to ensure that the code that sets up your pagination is inside the same (function($) {}) block in main.js - that way it will "capture" the function.
The other, dirtier way, would be to change var contentWaypoint= function... to window.contentWaypoint = function - then use window.contentWaypoint() whenever you need to invoke it. THere are much better ways to doing this, but that might get you going.

JavaScript classes, how does one apply "Separation of Concerns" and "Don't repeat Yourself" (DRY) in practice

I'm just in the process of learning how JavaScript classes work and I'm just looking for some advice on how to achieve something quite simple I hope regarding animating some elements.
I have created a class named myAnimation, the constructor takes in 1 argument which is an element. All its doing is fading a heading out and in, all very simple. It works fine when there is just one heading element on the page, I'm just not to sure how I go about getting it to work with more than one heading.
Please excuse my naivety with this; it's all very new to me, this is just a basic example I have managed to make myself to try and help myself understand how it works.
class myAnimation {
constructor(element) {
this.element = document.querySelector(element);
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
button.textContent = 'Show Heading';
}
}
}
const heading = new myAnimation('.heading');
const button = document.querySelector('.button');
button.addEventListener('click', () => {
heading.fadeOut(1);
});
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
After my comment I wanted to make the script run in a way I thought it might have been intended by the OP.
Even though it demonstrates what needs to be done in order to run properly, the entire base design proofs to be not fitting to what the OP really might need to achieve.
The class is called Animation but from the beginning it was intermingling element-animation and changing state of a single somehow globally scoped button.
Even though running now, the design does not proof to be a real fit because one now passes the element that is going to be animated and the button it shall interact with altogether into the constructor.
The functionality is grouped correctly, just the place and the naming doesn't really fit.
The OP might think about a next iteration step of the provided code ...
class Animation {
constructor(elementNode, buttonNode) {
this.element = elementNode;
this.button = buttonNode;
// only in case both elements were passed ...
if (elementNode && buttonNode) {
// couple them by event listening/handling.
buttonNode.addEventListener('click', () => {
// - accessing the `Animation` instance's `this` context
// gets assured by making use of an arrow function.
this.fadeOut(1);
});
}
}
fadeOut(time) {
if (this.element.classList.contains('fadeout-active')) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
this.button.textContent = 'Hide Heading';
} else {
this.element.style.opacity = 0;
this.element.style.transition = `all ${time}s ease`;
this.element.classList.add('fadeout-active');
this.button.textContent = 'Show Heading';
}
}
}
function initializeAnimations() {
// get list of all elements that have a `heading` class name.
const headingList = document.querySelectorAll('.heading');
// for each heading element do ...
headingList.forEach(function (headingNode) {
// ... access its parent element and query again for a single button.
const buttonNode = headingNode.parentElement.querySelector('.button');
// if the related button element exists ...
if (buttonNode) {
// ... create a new `Animation` instance.
new Animation(headingNode, buttonNode);
}
});
}
initializeAnimations();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro">
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main">
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... new day, next possible iteration step ...
The 2nd iteration separates concerns.
It does so by renaming the class and implementing only class specific behavior. Thus a FadeToggle class provides just toggle specific functionality.
The code then gets split into two functions that handle initialization. For better reuse the initializing code and the html structure need to be refactored into something more generic. The data attribute of each container that features a trigger-element for fading a target element will be used as a configuration storage that provides all necessary information for the initializing process. (One even can provide individual transition duration values.)
Last there is a handler function that is implemented in a way that it can be reused by bind in order to generate a closure which provides all the necessary data for each trigger-target couple.
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
const { trigger, target } = this;
if (target.isFadeoutActive()) {
trigger.textContent = 'Hide Heading';
} else {
trigger.textContent = 'Show Heading';
}
target.toggleFade();
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
const config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and binding both as context properties to the handler.
trigger,
target
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button">Hide Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Hide Heading</button>
</div>
... afternoon, improve the handling of state changes ...
There is still hard wired data, written directly into the code. In order to get rid of string-values that will be (re)rendered every time a toggle-change takes place one might give the data-based configuration-approach another chance.
This time each triggering element might feature a configuration that provides state depended values. Thus the initialization process needs to take care of retrieving this data and also of rendering it according to the initial state of a fade-toggle target.
This goal directly brings up the necessity of a render function for a trigger element because one needs to change a trigger's state not only initially but also with every fade-toggle.
And this again will change the handler function in a way that in addition it features bound state values too in order to delegate such data to the render process ...
class FadeToggle {
// a clean fade-toggle implementation.
constructor(elementNode, duration) {
duration = parseFloat(duration, 10);
duration = Number.isFinite(duration) ? duration : 1;
elementNode.style.opacity = 1;
elementNode.style.transition = `all ${ duration }s ease`;
this.element = elementNode;
}
isFadeoutActive() {
return this.element.classList.contains('fadeout-active');
}
toggleFade(duration) {
duration = parseFloat(duration, 10);
if (Number.isFinite(duration)) {
this.element.style.transitionDuration = `${ duration }s`;
}
if (this.isFadeoutActive()) {
this.element.style.opacity = 1;
this.element.classList.remove('fadeout-active');
} else {
this.element.style.opacity = 0;
this.element.classList.add('fadeout-active');
}
}
}
function renderTargetStateDependedTriggerText(target, trigger, fadeinText, fadeoutText) {
if ((fadeinText !== null) && (fadeoutText !== null)) {
if (target.isFadeoutActive()) {
trigger.textContent = fadeinText;
} else {
trigger.textContent = fadeoutText;
}
}
}
function handleFadeToggleWithBoundContext(/* evt */) {
// retrieve context data.
const { target, trigger, fadeinText, fadeoutText } = this;
target.toggleFade();
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
}
function initializeFadeToggle(elmNode) {
// parse an element node's fade-toggle configuration.
let config = JSON.parse(elmNode.dataset.fadeToggleConfig || null);
const selectors = (config && config.selectors);
if (selectors) {
try {
// query both the triggering and the target element
const trigger = elmNode.querySelector(selectors.trigger || null);
let target = elmNode.querySelector(selectors.target || null);
if (trigger && target) {
// create a `FadeToggle` target type.
target = new FadeToggle(target, config.duration);
// parse a trigger node's fade-toggle configuration and state.
const triggerStates = ((
JSON.parse(trigger.dataset.fadeToggleTriggerConfig || null)
|| {}
).states || {});
// get a trigger node's state change values.
const fadeinStateValues = (triggerStates.fadein || {});
const fadeoutStateValues = (triggerStates.fadeout || {});
// get a trigger node's state change text contents.
const fadeinText = fadeinStateValues.textContent || null;
const fadeoutText = fadeoutStateValues.textContent || null;
// rerender trigger node's initial text value.
renderTargetStateDependedTriggerText(
target,
trigger,
fadeinText,
fadeoutText
);
// couple trigger and target by event listening/handling ...
trigger.addEventListener(
'click',
handleFadeToggleWithBoundContext.bind({
// ... and by binding both and some text values
// that are sensitive to state changes
// as context properties to the handler.
target,
trigger,
fadeinText,
fadeoutText
})
);
}
} catch (exception) {
console.warn(exception.message, exception);
}
}
}
function initializeEveryFadeToggle() {
// get list of all elements that contain a fade-toggle configuration
const configContainerList = document.querySelectorAll('[data-fade-toggle-config]');
// do initialization for each container separately.
configContainerList.forEach(initializeFadeToggle);
}
initializeEveryFadeToggle();
.as-console-wrapper { max-height: 100%!important; top: 0; }
<div class="intro" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"},"duration":3}'>
<h1 class="heading">Intro Heading</h1>
<p>This is the intro section</p>
<button class="button" data-fade-toggle-trigger-config='{"states":{"fadeout":{"textContent":"Hide Heading"},"fadein":{"textContent":"Show Heading"}}}'>Toggle Heading</button>
</div>
<div class="main" data-fade-toggle-config='{"selectors":{"trigger":".button","target":".heading"}}'>
<h1 class="heading">Main Heading</h1>
<p>This is the main section</p>
<button class="button">Toggle Heading</button>
</div>
This is happening because document.querySelector(".button") only returns the first element with class .button (reference).
You might want to try document.querySelectorAll(".button") (reference) to add your event listeners.
(Though this will only toggle your first heading - for the very same reason. ;))

My animation is not working when re rendering my react component?

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.

adding and removing a class to activate keyframes in react

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

Categories