So i have a preloader that works, i just have some problems with my image component itself.
My image component below in my theory should after the first render get all of my image tags used in my project.
Then when the image event onLoad is fired, it should update a state which is didLoad and set it to true. When the page is rerendered again because of the updated state, it should check if didLoad is true, if it's true it should execute the useEffect inside the condition once and add 1 to the amount loaded.
And then i check if images is higher than 0, and images is equal to images loaded then remove preloader.
Obviously something is wrong since i can't get it to work, it needs to work with all images not one at a time because it's not a lazyload for the image itself, but the preloader for the whole page.
Ignore preload: false it's for when the preloader is removed, then it should play page animations.
/** #jsx jsx */
import React, { useContext, useEffect, useState } from "react";
import { css, jsx } from "#emotion/core";
import preloadContext from "../../context/preload.context";
const Img = ({ src, alt }) => {
const [didLoad, setLoad] = useState(false);
const [preload, setPreload] = useContext(preloadContext);
const imgStyle = css`
width: 100%;
height: 100%;
display: block;
object-fit: cover;
`;
useEffect(() => {
setPreload({
amount: preload.amount++,
preload: preload.preload,
isLoaded: preload.isLoaded,
});
}, []);
if (didLoad === true) {
useEffect(() => {
setPreload({
amount: preload.amount,
preload: preload.preload,
isLoaded: preload.isLoaded++,
});
}, []);
}
return (
<img
src={src}
onLoad={(e) => {
setLoad(true);
}}
css={imgStyle}
alt={alt}
onDragStart={(e) => e.preventDefault()}
/>
);
};
export default Img;
The answer is You can and You cannot.
We can : if you're trying to hold rendering of a certain component until an <img /> (Outside of this component is loaded)
We cannot : If you're trying to track loading of <img /> which is INSIDE of a not rendered component. In this case Image won't load until we render it's parent component.
For the first option here's how you can do it:
constructor(props) {
super(props);
this.state = { loaded: 0 };
this.handleImageLoaded = this.handleImageLoaded.bind(this);
this.image = React.createRef();
}
componentDidMount() {
const img = this.image.current;
if (img && img.complete) {
this.handleImageLoaded(10); // 100 / Number of images to track
}
}
handleImageLoaded(loadIncrement) {
if (!this.state.loaded)
this.setState({ loaded: loadIncrement });
}
render() {
return (
<div>
<img src='image.jpg' ref={this.image} onLoad={this.handleImageLoaded} />
{this.state.loaded >= 100 && <MyComponent />}
</div>
);
}
As mentioned in the comment you must divide 100 to number of images you wish to track and then one the image is loaded it will add the loading percentage to the parent state. One the loaded state reach 100 or above it will render <MyComponent />
How to animate React Component with react-spring library depend on props updating
I found this https://codesandbox.io/s/xpqq0ll5nw and want to implement the same with hooks
const Counter = ({quantity}) => {
const animation = useSpring({
transform: 'scale(1)',
from: { transform: 'scale(1.2)' },
});
return (
<animated.span style={animation}">
{quantity}
</animated.span>
)
}
Your Counter component is about right. The key here is the 'key' property. When the counter changes it alternates between 1 and 0. And each time the key changes React assign a new Counter component instance instead the old one repeating the initial animation of the Counter component.
class App extends Component {
state = { count: 0 };
increment = () => this.setState({ count: this.state.count + 1 });
render() {
return (
<Fragment>
<button onClick={this.increment}>Update!</button>
<Counter key={this.state.count % 2} quantity={this.state.count} />
</Fragment>
);
}
}
Here is the whole example: https://codesandbox.io/s/jjyp6p0yl9
I'm trying to use ScrollMagic with React and I'm stuck with the setPin() function which wraps my component within a div. As the component in mounted before the wrapping, everytime I update my states or props, React seems to think that the component doesn't exist and mount it again.
How can I manage to mount my component after the setPin function which does the wrapping?
componentDidMount () {
this.scrollController = new ScrollMagic.Controller()
this.initScroller()
}
initScroller () {
this.scene = new ScrollMagic.Scene({
triggerElement: this.node,
duration: window.innerHeight * 2,
triggerHook: 0,
reverse: true
})
this.scene.setPin(this.node, { pushFollowers: false })
this.scrollController.addScene(this.scene)
}
render () {
return (
<div
ref={node => {
this.node = node
}}
>
<div className={this.getClassNames()}>
<Hemicycle
deputiesCount={this.props.deputiesCount}
swissCount={this.props.swissCount}
/>
</div>
</div>
)
}
I have a React component that loads another component if it has it's initial local state altered. I can't get a clean test going because I need to set local state AND shallow render so the child component doesn't crash when the new component mounts because the redux store isn't there. It seems those two objectives are incompatible in Enzyme.
For the child component to display, things need to occur:
The component needs to receive a "response" props (any string will do)
The component need to have it's initial "started" local state updated to true. This is done with a button in the actual component.
This is creating some headaches with testing. Here is the actual line that determines what will be rendered:
let correctAnswer = this.props.response ? <div className="global-center"><h4 >{this.props.response}</h4><Score /></div> : <p className="quiz-p"><strong>QUESTION:</strong> {this.props.currentQuestion}</p>;
Here is my current Enzyme test:
it('displays score if response and usingQuiz prop give proper input', () => {
const wrapper = shallow(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
wrapper.setState({ started: true })
expect(wrapper.contains(<Score />)).toEqual(true)
});
I am using shallow, because any time I use mount, I get this:
Invariant Violation: Could not find "store" in either the context or props of "Connect(Score)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Score)".
Because the component is displayed through the parent, I can not simply select the disconnected version. Using shallow seems to correct this issue, but then I can not update the local state. When I tried this:
it('displays score if response and usingQuiz prop give proper input', () => {
const wrapper = shallow(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
wrapper.setState({ started: true })
expect(wrapper.contains(<Score />)).toEqual(true)
});
The test fails because shallow doesn't let the DOM get updated.
I need BOTH conditions to be met. I can do each condition individually, but when I need to BOTH 1) render a component inside a component (needs shallow or will freak about the store not being there), and 2) update local state (needs mount, not shallow), I can't get everything to work at once.
I've looked at chats about this topic, and it seems this is a legitimate limitation of Enzyme, at least in 2017. Has this issue been fixed? It's very difficult to test this.
Here is the full component if anyone needs it for reference:
import React from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Transition } from 'react-transition-group';
import { answerQuiz, deleteSession, getNewQuestion } from '../../actions/quiz';
import Score from '../Score/Score';
import './Quiz.css';
export class Quiz extends React.Component {
constructor(props) {
super(props);
// local state for local component changes
this.state = {
started: false
}
}
handleStart() {
this.setState({started: true})
}
handleClose() {
this.props.dispatch(deleteSession(this.props.sessionId))
}
handleSubmit(event) {
event.preventDefault();
if (this.props.correctAnswer && this.props.continue) {
this.props.dispatch(getNewQuestion(this.props.title, this.props.sessionId));
}
else if (this.props.continue) {
const { answer } = this.form;
this.props.dispatch(answerQuiz(this.props.title, answer.value, this.props.sessionId));
}
else {
this.props.dispatch(deleteSession(this.props.sessionId))
}
}
render() {
// Transition styles
const duration = 300;
const defaultStyle = {
opacity: 0,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
height: '100%',
width: '100%',
margin: '0px',
zIndex: 20,
top: '0px',
bottom: '0px',
left: '0px',
right: '0px',
position: 'fixed',
display: 'flex',
alignItems: 'center',
transition: `opacity ${duration}ms ease-in-out`
}
const transitionStyles = {
entering: { opacity: 0 },
entered: { opacity: 1 }
}
// Response text colors
const responseClasses = [];
if (this.props.response) {
if (this.props.response.includes("You're right!")) {
responseClasses.push('quiz-right-response')
}
else {
responseClasses.push('quiz-wrong-response');
}
}
// Answer radio buttons
let answers = this.props.answers.map((answer, idx) => (
<div key={idx} className="quiz-question">
<input type="radio" name="answer" value={answer} /> <span className="quiz-question-label">{answer}</span>
</div>
));
// Question or answer
let correctAnswer = this.props.response ? <div className="global-center"><h4 className={responseClasses.join(' ')}>{this.props.response}</h4><Score /></div>: <p className="quiz-p"><strong>QUESTION:</strong> {this.props.currentQuestion}</p>;
// Submit or next
let button = this.props.correctAnswer ? <button className="quiz-button-submit">Next</button> : <button className="quiz-button-submit">Submit</button>;
if(!this.props.continue) {
button = <button className="quiz-button-submit">End</button>
}
// content - is quiz started?
let content;
if(this.state.started) {
content = <div>
<h2 className="quiz-title">{this.props.title} Quiz</h2>
{ correctAnswer }
<form className="quiz-form" onSubmit={e => this.handleSubmit(e)} ref={form => this.form = form}>
{ answers }
{ button }
</form>
</div>
} else {
content = <div>
<h2 className="quiz-title">{this.props.title} Quiz</h2>
<p className="quiz-p">So you think you know about {this.props.title}? This quiz contains {this.props.quizLength} questions that will test your knowledge.<br /><br />
Good luck!</p>
<button className="quiz-button-start" onClick={() => this.handleStart()}>Start</button>
</div>
}
// Is quiz activated?
if (this.props.usingQuiz) {
return (
<Transition in={true} timeout={duration} appear={true}>
{(state) => (
<div style={{
...defaultStyle,
...transitionStyles[state]
}}>
{/* <div className="quiz-backdrop"> */}
<div className="quiz-main">
<div className="quiz-close" onClick={() => this.handleClose()}>
<i className="fas fa-times quiz-close-icon"></i>
</div>
{ content }
</div>
</div>
)}
</Transition >
)
}
else {
return <Redirect to="/" />;
}
}
}
const mapStateToProps = state => ({
usingQuiz: state.currentQuestion,
answers: state.answers,
currentQuestion: state.currentQuestion,
title: state.currentQuiz,
sessionId: state.sessionId,
correctAnswer: state.correctAnswer,
response: state.response,
continue: state.continue,
quizLength: state.quizLength,
score: state.score,
currentIndex: state.currentIndex
});
export default connect(mapStateToProps)(Quiz);
Here is my test using mount (this crashes due to a lack of store):
import React from 'react';
import { Quiz } from '../components/Quiz/Quiz';
import { Score } from '../components/Score/Score';
import { shallow, mount } from 'enzyme';
it('displays score if response and usingQuiz prop give proper input', () => {
const wrapper = mount(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
wrapper.setState({ started: true })
expect(wrapper.contains(<Score />)).toEqual(true)
});
});
This looks like a component that should be tested with mount(..).
How are you importing your connected component Score and Quiz?
I see that you are already correctly exporting Quiz component and default exporting the connected Quiz component.
Try importing with
import { Score } from '../Score/Score';
import { Quiz } from '../Quiz/Quiz';
in your test, and mount(..)ing. If you are importing from default export, you will get a connected component imported, which I think is the cause of the error.
Are you sure that Transition component let it's content to be displayed? I use this component and can't properly handle it in tests...
Can you for the test purposes alter your renders return with something like this:
if (this.props.usingQuiz) {
return (
<div>
{
this.state.started && this.props.response ?
(<Score />) :
(<p>No score</p>)
}
</div>
)
}
And your test can look something like this:
it('displays score if response and usingQuiz prop give proper input',() => {
const wrapper = shallow(<Quiz usingQuiz={true} answers={[]} response={'example'}/>);
expect(wrapper.find('p').text()).toBe('No score');
wrapper.setState({ started: true });
expect(wrapper.contains(<Score />)).toEqual(true);
});
I also tested shallows setState and little test like this works fine:
test('HeaderComponent properly opens login popup', () => {
const wrapper = shallow(<HeaderComponent />);
expect(wrapper.find('.search-btn').text()).toBe('');
wrapper.setState({ activeSearchModal: true });
expect(wrapper.find('.search-btn').text()).toBe('Cancel');
});
So I believe that shallow properly handle setState and the problem caused by some components inside your render.
The reason you are getting that error is because you're trying to test the wrapper component generated by calling connect()(). That wrapper component expects to have access to a Redux store. Normally that store is available as context.store, because at the top of your component hierarchy you'd have a <Provider store={myStore} />. However, you're rendering your connected component by itself, with no store, so it's throwing an error.
Also, if you are trying to test a component inside a component, may full DOM renderer may be the solution.
If you need to force the component to update, Enzyme has your back. It offers update() and if you call update() on a reference to a component that will force the component to re-render itself.
I need to make an image as a background for the whole first screen as a SplashScreen and after some fixed time it shows the other component.
I created two components Home and SplashScreen and here is the code i'm using:
componentDidMount() {
SplashScreen.hide();
}
render() {
return(
<View>
<SplashScreen/>
<Home/>
</View>
)
}
Please any help or idea
You need to implement this in Javascript land.
Top most component would hold a flag indicating to render splash screen.
Update this flag after specified amount of time and render desired content.
Dummy implementation may look like this...
class App extends Component {
state = {
ready: false,
}
componentDidMount () {
setTimeout(() => {
this.setState({ ready: true })
}, 5000)
}
render() {
if (this.state.ready === false) {
return <Splash />
}
return this.props.children;
}
}
// Usage example:
<App>
<RouterOrSomething />
</App>