The element works when using an external node module but not when using a locally downloaded node module but I can't work out why?
here's the Swipi-cards library:
https://github.com/riolcrt/swipi-cards/blob/master/demo/index.html
My code so far(working) but when I use local node module as source it doesn't any fixes?
import React, { useState, useEffect } from 'react';
import Data from '../Data/webApps_data';
function Webapps() {
const [loading_animation, setloading_animation] = useState(false);
const [arrayChecker, set_arrayChecker] = useState(0);
useEffect(() => {
if (loading_animation === false) {
setTimeout(() => {
setloading_animation(!loading_animation);
console.log()
}, 100);
}
const script = document.createElement('script');
script.src = "https://unpkg.com/swipi-cards#1.0.0/dist/swipi-cards/swipi-cards.js";
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
}
}, [loading_animation]);
const arrayLimiter = () => {
if (arrayChecker < (Data.length - 1)) {
set_arrayChecker(arrayChecker + 1)
} else {
set_arrayChecker(0)
}
console.log(Data);
};
const filteredData = Data[arrayChecker];
const textTransition = loading_animation ? 'text_transition ease' : 'text_transition';
const elementTransition = loading_animation ? 'element_transition ease' : 'element_transition';
//swipicard script
return (
<div className='webAppStyles'>
<rg-swipi-cards stack-offset-y="0.3" class='hydrated'>
<rg-swipi-card left-color='green' right-color='green' class='hydrated'>
<p>test1</p>
</rg-swipi-card>
<rg-swipi-card left-color='green' right-color='green' class='hydrated'>
<p>test2</p>
</rg-swipi-card>
</rg-swipi-cards>
</div>
)
}
scss styles:
rg-swipi-cards {
display: flex;
align-self: center;
background: chocolate;
width: 350px !important;
height: 400px !important;
align-items: center;
justify-content: center;
rg-swipi-card {
width: 100%;
position: absolute;
transition: all 0.5s ease-in-out 0s;
z-index: 4;
opacity: 1.33333;
}
p {
text-align: center;
}
}
ok so using this as the source works but I don't know why this must mean the node module doesn't work any ideas?
script.src = "https://unpkg.com/swipi-cards#1.0.0/dist/swipi-cards/swipi-cards.js";
Related
I am still learning React and am trying to parse text from an API call into json objects while displaying progress in a progress bar.
Below, Home.js uses useEffect hook to
call getData that grabs (lots of) API text data
call parseText to parse returned text
call parseSeriesId to parse returned results
set seriesIdParts
Finally, seriesIdParts is passed to Occupation.js as props. That works fine and working code is displayed below.
My problem is that I want to make the progress bar disappear when parseText is finished. I found a post that did it using a ternary operator, so trying that I changed this:
<ProgressBar percentage={percentage}/>
to this:
{percentage >= 100 ? console.log("Done.") : <ProgressBar percentage={percentage}/>
and now the progressBar disappears, but it no longer populates green. I can log the percentage value just fine in both ProgressBar.js and Filler.js, so it seems to be that the Filler width element is not picking up the percentage value increment:
<div className="filler" style={{ width: `${percentage}%`}}>
How do I fix this?
App.js
import React from 'react';
import './App.css';
import Home from './components/Home';
function App() {
return (
<div className="App">
<Home />
</div>
);
}
export default App;
Home.js
import React from 'react';
import { useState, useEffect } from 'react';
import Occupation from './Occupation';
import ProgressBar from './ProgressBar';
const Home = () => {
const [seriesIdParts, setSeriesIdParts] = useState([]);
const [percentage, setPercentage] = useState(0);
async function getData(url) {
const response = await fetch(url);
if( response.status !== 200 ) {
throw new Error('Problem calling ' + url);
}
return response.text();
}
function parseSeriesId(data) {
let results = new Set()
data.forEach(function(value) {
let areaCode = value.seriesId.substring(4, 11);
let industry = value.seriesId.substring(11, 17);
let occupation = value.seriesId.substring(17, 23);
let dataType = value.seriesId.substring(23, value.seriesId.length);
let seriesIdVal = value.value;
if( dataType === '04') {
let result = {areaCode: areaCode, industry: industry, occupation: occupation, dataType: dataType, seriesIdVal: seriesIdVal};
results.add(result);
}
})
return results;
}
useEffect(() => {
getData('https://download.bls.gov/pub/time.series/oe/oe.data.0.Current')
.then(data => {
const parseText = function(data) {
data = data.split('\n');
let localNum = 1090;
let results = new Set();
let intervalAmount = 100 / localNum;
for( let i=1; i<=localNum; i++ ) {
setPercentage((i * intervalAmount));
let line = data[i];
if( line !== "") {
line = line.split('\t');
let seriesIdValue = line[0].trim();
let valueValue = line[3].trim();
if (valueValue !== '-') {
results.add({'seriesId': seriesIdValue, 'value': valueValue});
}
}
}
return results;
}
return parseText(data, 100);
})
.then(data => {
return parseSeriesId(data);
})
.then(data => {
setSeriesIdParts(data);
})
.catch(err => console.log(err));
}, [percentage]);
return(
<div className="homeComponent">
<label id="parsetext" >
Parsing Text...
<ProgressBar percentage={percentage}/>
</label>
{seriesIdParts && <Occupation seriesIdParts={seriesIdParts}/>}
</div>
);
}
export default Home;
ProgressBar.js
import React, { useState } from 'react';
import Filler from './Filler';
const ProgressBar = ({percentage}) => {
// console.log('Pbar: ', percentage); <== Displays fine!
return (
<div className="progressbar">
<Filler percentage={percentage}/>
</div>
);
}
export default ProgressBar;
Filler.js
import React from 'react';
const Filler = ({percentage}) => {
return (
<div className="filler" style={{ width: `${percentage}%`}}>
{console.log('filler: ' + percentage)}. <== Displays fine!
</div>
);
}
export default Filler;
Occupation.js
import React from 'react';
import { useState, useEffect } from 'react';
const Occupation = ({seriesIdParts}) => {
console.log('seriesIdParts in Occupation:', seriesIdParts.size) <== Displays fine!
//other code...
};
export default Occupation;
App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
#media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
#keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.progressbar {
margin: 4em auto;
width: 80%;
height: 2em;
background-color: lightgray;
border-radius: 30px;
}
.filler {
height: inherit;
width: 0px;
background-color: green;
border-radius: inherit;
}
#parsetext {
text-align: 'center';
margin: 4em 0 0;
}
UPDATE: If there is a better way to make the progress bar disappear, I'm open to suggestions!
What if you set the height of the filler class to like 20px instead of inherit just to try?
.filler {
height: inherit;
width: 0px;
background-color: green;
border-radius: inherit;
}
I tried to test just that div and I couldn't see it with "inherit"
I'm having an issue where cookies are not being set and popup message doesn't seem to hide itself I would admit I'm an amateur with javascript and this is my first time dealing with cookies. I had it working at one point but I can't seem to recreate what made it work
#consent-popup {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: .5rem;
background-color: #717171;
color: white;
opacity: 1;
transition: opacity .8s ease;
&.hidden {
opacity: 0;
}
}
<div id="consent-popup" class="hidden">
<p>short View our Terms and conditions and Privacy policy. I <a id="accept" href="#">ACCEPT</a></p>
</div>
const cookieStorage = {
getItem: (key) => {
const cookies = document.cookie
.split(';')
.map(cookie => cookie.split('='))
.reduce((acc, [key, value]) => ({ ...acc, [key.trim()]: value}), {});
return cookies[key];
},
setItem(key, value) {
document.cookie = `${key}=${value}`;
},
};
const storageType = cookieStorage;
const consentPropertyName = 'rnc_cookies';
const shouldShowPopup = () => !storageType.getItem(consentPropertyName);
const saveToStorage = () => storageType.setItem(consentPropertyName, true);
window.onload = () => {
const consentPopup = document.getElementById('consent-popup');
const acceptBtn = document.getElementById('accept');
const acceptFn = event => {
saveToStorage(storageType)
consentPopup.classList.add('hidden');
};
acceptBtn.addEventListener('click', acceptFn);
if (shouldShowPopup(storageType)) {
setTimeout(() => {
consentPopup.classList.remove('hidden');
}, 2000);
}
};
I am working on a progress bar (Eventually..) and I want to stop the animation (calling cancelAnimationRequest) when reaching a certain value (10, 100, ..., N) and reset it to 0.
However, with my current code, it resets to 0 but keeps running indefinitely. I think I might have something wrong in this part of the code:
setCount((prevCount) => {
console.log('requestRef.current', requestRef.current, prevCount);
if (prevCount < 10) return prevCount + deltaTime * 0.001;
// Trying to cancel the animation here and reset to 0:
cancelAnimationFrame(requestRef.current);
return 0;
});
This is the whole example:
const Counter = () => {
const [count, setCount] = React.useState(0);
// Use useRef for mutable variables that we want to persist
// without triggering a re-render on their change:
const requestRef = React.useRef();
const previousTimeRef = React.useRef();
const animate = (time) => {
if (previousTimeRef.current != undefined) {
const deltaTime = time - previousTimeRef.current;
// Pass on a function to the setter of the state
// to make sure we always have the latest state:
setCount((prevCount) => {
console.log('requestRef.current', requestRef.current, prevCount);
if (prevCount < 10) return prevCount + deltaTime * 0.001;
// Trying to cancel the animation here and reset to 0:
cancelAnimationFrame(requestRef.current);
return 0;
});
}
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animate);
}
React.useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
}, []);
return <div>{ Math.round(count) }</div>;
}
ReactDOM.render(<Counter />, document.getElementById('app'));
html {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
body {
font-size: 60px;
font-weight: 700;
font-family: 'Roboto Mono', monospace;
color: #5D9199;
background-color: #A3E3ED;
}
.as-console-wrapper {
max-height: 66px !important;
}
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
Code pen: https://codepen.io/fr-nevin/pen/RwrLmPd
The main problem with your code is that you are trying to cancel an update that has already been executed. Instead, you can just avoid requesting that last update that you don't need. You can see the problem and a simple solution for that below:
const Counter = () => {
const [count, setCount] = React.useState(0);
const requestRef = React.useRef();
const previousTimeRef = React.useRef(0);
const animate = React.useCallback((time) => {
console.log(' RUN:', requestRef.current);
setCount((prevCount) => {
const deltaTime = time - previousTimeRef.current;
const nextCount = prevCount + deltaTime * 0.001;
// We add 1 to the limit value to make sure the last valid value is
// also displayed for one whole "frame":
if (nextCount >= 11) {
console.log(' CANCEL:', requestRef.current, '(this won\'t work as inteneded)');
// This won't work:
// cancelAnimationFrame(requestRef.current);
// Instead, let's use this Ref to avoid calling `requestAnimationFrame` again:
requestRef.current = null;
}
return nextCount >= 11 ? 0 : nextCount;
});
// If we have already reached the limit value, don't call `requestAnimationFrame` again:
if (requestRef.current !== null) {
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animate);
console.log('- SCHEDULE:', requestRef.current);
}
}, []);
React.useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
}, []);
// This floors the value:
// See https://stackoverflow.com/questions/7487977/using-bitwise-or-0-to-floor-a-number.
return (<div>{ count | 0 } / 10</div>);
};
ReactDOM.render(<Counter />, document.getElementById('app'));
html {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
body {
font-size: 60px;
font-weight: 700;
font-family: 'Roboto Mono', monospace;
color: #5D9199;
background-color: #A3E3ED;
}
.as-console-wrapper {
max-height: 66px !important;
}
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
In any case, you are also updating the state many more times than actually needed, which you can avoid by using refs and the timestamp (time) provided by requestAnimationFrame to keep track of the current and next/target counter values. You are still going to call the requestAnimationFrame update function the same number of times, but you will only update the state (setCount(...)) once you know the change is going to be reflected in the UI.
const Counter = ({ max = 10, rate = 0.001, location }) => {
const limit = max + 1;
const [count, setCount] = React.useState(0);
const t0Ref = React.useRef(Date.now());
const requestRef = React.useRef();
const targetValueRef = React.useRef(1);
const animate = React.useCallback(() => {
// No need to keep track of the previous time, store initial time instead. Note we can't
// use the time param provided by requestAnimationFrame to the callback, as that one won't
// be reset when the `location` changes:
const time = Date.now() - t0Ref.current;
const nextValue = time * rate;
if (nextValue >= limit) {
console.log('Reset to 0');
setCount(0);
return;
}
const targetValue = targetValueRef.current;
if (nextValue >= targetValue) {
console.log(`Update ${ targetValue - 1 } -> ${ nextValue | 0 }`);
setCount(targetValue);
targetValueRef.current = targetValue + 1;
}
requestRef.current = requestAnimationFrame(animate);
}, []);
React.useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
}, []);
React.useEffect(() => {
// Reset counter if `location` changes, but there's no need to call `cancelAnimationFrame` .
setCount(0);
t0Ref.current = Date.now();
targetValueRef.current = 1;
}, [location]);
return (<div className="counter">{ count } / { max }</div>);
};
const App = () => {
const [fakeLocation, setFakeLocation] = React.useState('/');
const handleButtonClicked = React.useCallback(() => {
setFakeLocation(`/${ Math.random().toString(36).slice(2) }`);
}, []);
return (<div>
<span className="location">Fake Location: { fakeLocation }</span>
<Counter max={ 10 } location={ fakeLocation } />
<button className="button" onClick={ handleButtonClicked }>Update Parent</button>
</div>);
};
ReactDOM.render(<App />, document.getElementById('app'));
html {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
body {
font-family: 'Roboto Mono', monospace;
color: #5D9199;
background-color: #A3E3ED;
}
.location {
font-size: 16px;
}
.counter {
font-size: 60px;
font-weight: 700;
}
.button {
border: 2px solid #5D9199;
padding: 8px;
margin: 0;
font-family: 'Roboto Mono', monospace;
color: #5D9199;
background: transparent;
outline: none;
}
.as-console-wrapper {
max-height: 66px !important;
}
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
I have literally tried for a few hours to replicate a clickable ticker, much like they have at the very top of this site: https://www.thebay.com/
I'm confused about what triggers useEffect and long story short, I can't come up with a solution that keeps the ticker moving AND also gives the option of clicking forward/backwards via arrows. Clicking the arrow should not permanently pause the ticker.
function Ticker() {
const [tickerDisplay, setTickerDisplay] = useState('Free In-store Pickup')
const [tickerIndex, setTickerIndex] = useState(0)
const [arrowClicked, setArrowClicked] = useState(false)
const notices = [
'Easy Returns within 30 Days of Purchase',
'Free Shipping on $99+ Orders',
'Free In-store Pickup',
]
const handleClick = (side) => {
setArrowClicked(true)
switch (side) {
case 'left':
setTickerIndex(
tickerIndex === 0 ? notices.length - 1 : tickerIndex - 1
)
break
case 'right':
setTickerIndex(
tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
)
break
default:
console.log('something went wrong')
break
}
}
useEffect(() => {
if (arrowClicked) {
setTickerDisplay(notices[tickerIndex])
setTickerIndex(
tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
)
setArrowClicked(false)
return
}
setTimeout(() => {
setTickerDisplay(notices[tickerIndex])
setTickerIndex(
tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
)
console.log('This will run every 6 seconds!')
}, 6000)
}, [tickerIndex, notices, tickerDisplay, arrowClicked])
return (
<IconContext.Provider value={{ className: 'ticker-icons-provider' }}>
<div className='ticker'>
<FaChevronLeft onClick={() => handleClick('left')} />
<div className='ticker_msg-wrapper'>{tickerDisplay}</div>
<FaChevronRight onClick={() => handleClick('right')} />
</div>
</IconContext.Provider>
)
}
export default Ticker
What is the best way to code this component?
This is not a work of art and probably some things could've been done better.
Hope that suits you.
const { useRef, useState, useEffect } = React;
const getItems = () => Promise.resolve(['All of our questions are now open', 'Answers extended: 72 hours after questions open', 'Post a question or get an answer', 'Free badges on 20k points'])
const Ticker = ({onPrevious, onNext, items, currentIndex}) => {
const ref = useRef(null);
const [size, setSize] = useState({
width: 0,
widthPx: '0px',
height: 0,
heightPx: '0px'
})
useEffect(() => {
if(ref && ref.current) {
const {width, height} = ref.current.getBoundingClientRect();
setSize({
width,
widthPx: `${width}px`,
height,
height: `${height}px`
})
}
}, [ref]);
const calculateStyleForItem = (index) => {
return {
width: size.width,
transform: `translateX(${0}px)`
}
}
const calculateStyleForContainer = () => {
return {
width: `${size.width * (items.length + 1)}px`,
transform: `translateX(${-currentIndex * size.width + 2 * size.width}px)`
}
}
return <div ref={ref} className="ticker">
<div style={{width: size.widthPx, height: size.heightPx}} className="ticker__foreground">
<div onClick={onPrevious} className="arrow">{'<'}</div>
<div onClick={onNext} className="arrow">{'>'}</div>
</div>
<div>
<div style={calculateStyleForContainer()} className="ticker__values">
{items.map((value, index) => <div key={index} style={calculateStyleForItem(index)}className="ticker__value">{value}</div>)}
</div>
</div>
</div>
}
const App = () => {
const [items, setItems] = useState([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [clicked, setClicked] = useState(false);
useEffect(() => {
let isUnmounted = false;
getItems()
.then(items => {
if(isUnmounted) {
return
}
setItems(items);
})
return () => {
isUnmounted = true;
}
}, [])
useEffect(() => {
if(!items.length) {
return () => {
}
}
let handle = null;
const loop = () => {
if(!clicked) {
onNext(null);
}
setClicked(false);
handle = setTimeout(loop, 2000);
}
handle = setTimeout(loop, 2000);
return () => {
clearTimeout(handle);
}
}, [items, clicked])
const onPrevious = () => {
setClicked(true);
setCurrentIndex(index => (index - 1) > -1 ? index - 1 : items.length - 1)
}
const onNext = (programmatically) => {
if(programmatically) {
setClicked(programmatically);
}
setCurrentIndex(index => (index + 1) % items.length)
}
return <div>
{items.length ? <Ticker onPrevious={onPrevious} onNext={onNext} currentIndex={currentIndex} items={items}/> : 'Loading'}
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
html, body {
box-sizing: border-box;
margin: 0;
}
.ticker {
display: flex;
justify-content: center;
align-items: center;
background: black;
font-size: 1rem;
color: white;
font-weight: bold;
padding: 1rem;
overflow: hidden;
}
.ticker__foreground {
position: absolute;
z-index: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.ticker__values {
transition: all .3s ease-in;
}
.ticker__value {
text-align: center;
display: inline-block;
vertical-align: middle;
float: none;
}
.arrow {
font-size: 1.5rem;
cursor: pointer;
padding: 1rem;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
I am trying to create star rating where the functionality has to be following:
In read mode, the stars are shown as per average (should support 100%
i.e 5 or 96% i.e 4.6) in write mode, the user can only rate 1, 1.5, 2,
2.5 etc not 2.6
The read mode is working as expected but is having problem with write mode.
The problem in write mode is I cannot update the rating with non-decimal value from 1 to 5 and also half value like 1.5, 2.5, 3.5 etc. On hovering how do i decide if my mouse pointer is in the full star or half of star? Can anyone look at this, please?
I have created a sandbox for showing the demo
Here it is
https://codesandbox.io/s/9l6kmnw7vw
The code is as follow
UPDATED CODE
// #flow
import React from "react";
import styled, { css } from "styled-components";
const StyledIcon = styled.i`
display: inline-block;
width: 12px;
overflow: hidden;
direction: ${props => props.direction && props.direction};
${props => props.css && css(...props.css)};
`;
const StyledRating = styled.div`
unicode-bidi: bidi-override;
font-size: 25px;
height: 25px;
width: 125px;
margin: 0 auto;
position: relative;
padding: 0;
text-shadow: 0px 1px 0 #a2a2a2;
color: grey;
`;
const TopStyledRating = styled.div`
padding: 0;
position: absolute;
z-index: 1;
display: block;
top: 0;
left: 0;
overflow: hidden;
${props => props.css && css(...props.css)};
width: ${props => props.width && props.width};
`;
const BottomStyledRating = styled.div`
padding: 0;
display: block;
z-index: 0;
`;
class Rating extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
rating: this.props.rating || null,
// eslint-disable-next-line
temp_rating: null
};
}
handleMouseover(rating) {
console.log("rating", rating);
this.setState(prev => ({
rating,
// eslint-disable-next-line
temp_rating: prev.rating
}));
}
handleMouseout() {
this.setState(prev => ({
rating: prev.temp_rating
}));
}
rate(rating) {
this.setState({
rating,
// eslint-disable-next-line
temp_rating: rating
});
}
calculateWidth = value => {
const { total } = this.props;
const { rating } = this.state;
return Math.floor((rating / total) * 100).toFixed(2) + "%";
};
render() {
const { disabled, isReadonly } = this.props;
const { rating } = this.state;
const topStars = [];
const bottomStars = [];
const writableStars = [];
console.log("rating", rating);
// eslint-disable-next-line
if (isReadonly) {
for (let i = 0; i < 5; i++) {
topStars.push(<span>★</span>);
}
for (let i = 0; i < 5; i++) {
bottomStars.push(<span>★</span>);
}
} else {
// eslint-disable-next-line
for (let i = 0; i < 10; i++) {
let klass = "star_border";
if (rating >= i && rating !== null) {
klass = "star";
}
writableStars.push(
<StyledIcon
direction={i % 2 === 0 ? "ltr" : "rtl"}
className="material-icons"
css={this.props.css}
onMouseOver={() => !disabled && this.handleMouseover(i)}
onFocus={() => !disabled && this.handleMouseover(i)}
onClick={() => !disabled && this.rate(i)}
onMouseOut={() => !disabled && this.handleMouseout()}
onBlur={() => !disabled && this.handleMouseout()}
>
{klass}
</StyledIcon>
);
}
}
return (
<React.Fragment>
{isReadonly ? (
<StyledRating>
<TopStyledRating
css={this.props.css}
width={this.calculateWidth(this.props.rating)}
>
{topStars}
</TopStyledRating>
<BottomStyledRating>{bottomStars}</BottomStyledRating>
</StyledRating>
) : (
<React.Fragment>
{rating}
{writableStars}
</React.Fragment>
)}
</React.Fragment>
);
}
}
Rating.defaultProps = {
css: "",
disabled: false
};
export default Rating;
Now the writable stars is separately done to show the stars status when hovering and clicking but when I am supplying rating as 5 it is filling the third stars instead of 5th.
I think your current problem seems to be with where your mouse event is set, as you are handling it on the individual stars, they disappear, and trigger a mouseout event, causing this constant switch in visibility.
I would rather set the detection of the rating on the outer div, and then track where the mouse is in relation to the div, and set the width of the writable stars according to that.
I tried to make a sample from scratch, that shows how you could handle the changes from the outer div. I am sure the formula I used can be simplified still, but okay, this was just to demonstrate how it can work.
const { Component } = React;
const getRating = x => (parseInt(x / 20) * 20 + (x % 20 >= 13 ? 20 : x % 20 >= 7 ? 10 : 0));
class Rating extends Component {
constructor() {
super();
this.state = {
appliedRating: '86%'
};
this.setParentElement = this.setParentElement.bind( this );
this.handleMouseOver = this.handleMouseOver.bind( this );
this.applyRating = this.applyRating.bind( this );
this.reset = this.reset.bind( this );
this.stopReset = this.stopReset.bind( this );
}
stopReset() {
clearTimeout( this.resetTimeout );
}
setParentElement(e) {
this.parentElement = e;
}
handleMouseOver(e) {
this.stopReset();
if (e.currentTarget !== this.parentElement) {
return;
}
const targetRating = getRating(e.clientX - this.parentElement.offsetLeft);
if (this.state.setRating !== targetRating) {
this.setState({
setRating: targetRating
});
}
}
applyRating(e) {
this.setState({
currentRating: this.state.setRating
});
}
reset(e) {
this.resetTimeout = setTimeout(() => this.setState( { setRating: null } ), 50 );
}
renderStars( width, ...classes ) {
return (
<div
onMouseEnter={this.stopReset}
className={ ['flex-rating', ...classes].join(' ')}
style={{width}}>
<span onMouseEnter={this.stopReset} className="star">★</span>
<span onMouseEnter={this.stopReset} className="star">★</span>
<span onMouseEnter={this.stopReset} className="star">★</span>
<span onMouseEnter={this.stopReset} className="star">★</span>
<span onMouseEnter={this.stopReset} className="star">★</span>
</div>
);
}
renderFixed() {
return this.renderStars('100%', 'fixed');
}
renderReadOnlyRating() {
const { appliedRating } = this.state;
return this.renderStars( appliedRating, 'readonly' );
}
renderWriteRating() {
let { setRating, currentRating } = this.state;
if (setRating === 0) {
setRating = '0%';
}
if (currentRating === undefined) {
currentRating = '100%';
}
return this.renderStars( setRating || currentRating, 'writable' );
}
render() {
return (
<div>
<div
ref={ this.setParentElement }
className="rating"
onMouseMove={ this.handleMouseOver }
onMouseOut={ this.reset }
onClick={ this.applyRating }>
{ this.renderFixed() }
{ this.renderReadOnlyRating() }
{ this.renderWriteRating() }
</div>
<div>Current rating: { ( ( this.state.currentRating || 0 ) / 20) }</div>
</div>
);
}
}
ReactDOM.render( <Rating />, document.getElementById('container') );
body { margin: 50px; }
.rating {
font-family: 'Courier new';
font-size: 16px;
position: relative;
display: inline-block;
width: 100px;
height: 25px;
align-items: flex-start;
justify-content: center;
align-content: center;
background-color: white;
}
.flex-rating {
position: absolute;
top: 0;
left: 0;
display: flex;
height: 100%;
overflow: hidden;
cursor: pointer;
}
.fixed {
color: black;
font-size: 1.1em;
font-weight: bold;
}
.readonly {
color: silver;
font-weight: bold;
}
.writable {
color: blue;
background-color: rgba(100, 100, 100, .5);
}
.star {
text-align: center;
width: 20px;
max-width: 20px;
min-width: 20px;
}
<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>
<div id="container"></div>