So for an app, I decided to show an equation (for an app), and have real-time value updates for each value (with a button to show/hide the equation at will). So for that, not only do I have an equation component in the main app component (App.js), I also decided to put the equation component in the same file as my values component (value.js), in order to access the values and pass them as props to the equation component. I figured that I could hide the equation component in the value component, so only the equation component in the main app component is showing, and so I can also access the props at the same time, so I typed in something like this:
<Equation
className="hide"
FLoad={this.state.FLoad}
DLoad={this.state.DLoad}
DLowerBack={this.state.DLowerBack}
FTorso={this.state.FTorso}
DTorso={this.state.DTorso}
FLowerBack={this.state.FLowerBack}/>
and the "hide" class looks something like:
.hide {
display: none;
}
However, when I run my app, both show up simultaneously, and the two equations overlap each other. Why is that? Why doesn't the equation in the values component hide, even with the proper CSS tags? I hope someone can help me out. Thanks in advance.
EDIT: Here's some more code for some more context
render() {
return(
<div className="Equation">Force of Lower Back () = (-1 x (Force Load x ( Distance Load / Distance Lower Back)) + (-1 x Force Torso x (Distance Torso / Distance Lower Back))
<div>Upward Force From Legs () = Force Load + Force Torso + Force Lower Back</div></div>
);
}
}
^The code in the equation component
EDIT #2:
{equation ? <div>
<Equation/>
<button className="EquationButton" onClick = {() => equationVisibility(!equation)}>Hide Equation</button>
</div> :
<button className="EquationButton" onClick = {() => equationVisibility(!equation)}>Show Equation</button>}
<Input />
</div>
^Here's the code in the main app component as well. (The Input component being the values component I was talking about earlier)
If I understood correctly, the className="hide" is not working on your Equation component. It can be due to this Equation not using it internally. You should receive the className in the Equation and apply it manually to the internal component that needs to be hidden.
const Equation = ({ className }) => (
<div className={className} />
)
However, I would recommend you to create a prop to hide it or not, this way:
const Equation = ({ hide }) => {
if (hide) {
return null
}
return <div>{...}</div>
}
So you can use it this way:
<Equation
hide
// other props
/>
EDIT:
Based on the new piece of code that you showed, you can do this:
render() {
return(
<div className={`Equation ${this.props.className}`}> // <--- add this
Force of Lower Back () = (-1 x (Force Load x ( Distance Load / Distance Lower Back)) + (-1 x Force Torso x (Distance Torso / Distance Lower Back))
<div>
Upward Force From Legs () = Force Load + Force Torso + Force Lower Back
</div>
</div>
);
}
When you use a custom component like that you need to pass the appropriate props. Maybe you should add a className prop to the component and then add the following line:
<element className={className} />
You can use the ... spread operator to add all of the properties:
const Equation = ({...props}) => {
return (
<element {...props} />
)
}
Related
I'm using the react-scrollbar package to render a scrollbar for my my content. What I also want is a arrow button that, on click, moves to a certain scrollbar area. The problem is, I'm trying to style (marginTop) a class inside my component.
This is my attempt:
// MY COMPONENT
scrollToNextUpload = () => {
const NextUpload = 400
this.setState({ marginTop : this.state.marginTop + NextUpload }, () => document.getElementsByClassName('scrollarea-content')[0].style.marginTop = "'" + this.state.marginTop + "px'")
}
// MY RENDER
render () {
<ScrollArea>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
What is actually rendered
<div class='scrollarea'>
<div class='scrollarea-content'>
// my content
<div onClick={this.scrollToNext}></div>
</div>
</div>
What I want
To make my area with the scrollbar scroll, I have to add a marginTop style to the 'scrollarea-content'. I could do this by passing props to the < ScrollArea > and then use them inside the installed package; but I'm trying to avoid altering the original package content.Also, is there another way how I could scroll by click and is there someone else who's experienced with that NPM Package?
Most libraries give props to apply style to child components, in this library you can pass a className to the contentClassName or use inline style in contentStyle prop :
<ScrollArea contentStyle={{ marginTop: 10 }}>
An another way is to write css to add style to the scrollarea-content class.
In a .css file :
.scrollarea-content {
margin-top: 10px;
}
Edit: In your case you can programatically change the marginTop style by using the props like this :
scrollToNextUpload = () => {
const NextUpload = 400;
this.setState(prevState => ({ marginTop : prevState.marginTop + NextUpload }));
}
render () {
<ScrollArea contentStyle={{ marginTop: this.state.marginTop }}>
// my content
<div onClick={this.scrollToNext}></div>
</ScrollArea>
}
Note the use of a functional setState to prevent inconsistencies when next state value depends on the previous state.
I have tried finding the answer to this on StackOverflow and there are some related posts (e.g. React Child Component Not Updating After Parent State Change) but I want to understand why this is not working...
I have a React application that will display a layout of character cards (that is, each card displays a different character). It uses a child component, CharacterBoard, that lays out the CharacterCards, which would be a grandchild component. I pass the characters down from the App to the CharacterBoard as props, and CharacterBoard in turn maps these out the CharacterCards.
The problem is that I want the state of the character to change when I click on one of them. Specifically, I want the revealed field to change. However, even though the state change is reflected in the array of characters in the App (that is, the revealed field changes correctly), and the change is reflected in the array of characters in CharacterBoard, but not in CharacterCard. In fact, my mapping does not seem to be called at all in CharacterBoard when the props change.
Do I need to use something like getDerivedStateFromProps in CharacterBoard and set the state of that component and then use the state to map the values down to CharacterCard? If so, why?
In short (tl;dr), can you pass props on down through the component chain and map them out along the way and still have all changes reflected automatically?
Thanks for any guidance.
If it helps, the render method of my App is
render() {
const {state: {characters}} = this
return (
<div>
<header>
</header>
<main>
<CharacterBoard
onCardSelected={this.onCardSelected}
rowSize={logic.ROW_SIZE}
characters={characters}
cardSize={this.CARD_SIZE}/>
</main>
</div>
);
}
that of CharacterBoard is
render() {
const {props: {characters, rowSize, cardSize,onCardSelected}} = this
const rowUnit = 12 / rowSize
const cardLayout = characters
.map((character, i) => (
<Col xs={6} sm={rowUnit} key={character.name}>
<CharacterCard
onCardSelected = {onCardSelected}
key={i + Math.random()}
character={character}
cardSize={cardSize}
/>
</Col>
)
)
return (
<div>
<Container>
<Row>
{cardLayout}
</Row>
</Container>
</div>
)
}
and finally CharacterCard has this render method
render() {
const {props: {character, cardSize}} = this
const {thumbnail, revealed} = character
const imgURL = `${thumbnail.path}/${cardSize}.${thumbnail.extension}`
const topCardClass = classNames('characterCard__card-back', {'characterCard__card-back--hidden': revealed})
console.log(revealed)
return < a href="/#" onClick={this.onCardSelected}>
<div className='characterCard__card'>
<div className={topCardClass}>
<img src="/images/card_back.png" alt=""/>
</div>
< div className='characterCard__card-front'>< img alt=''
src={imgURL}/>
</div>
</div>
</a>
}
Doh! A simple forgetting to setState in App. Knowing that it should work made me go back through the code one more time and see that, indeed, it was a stupid error on my part.
I'm using Redux in my app, inside a Component I want to scroll to an specific div tag when a change in the store happens.
I have the Redux part working so it triggers the componentDidUpdate() method (I routed to this compoennt view already).
The problem as far as I can tell, is that the method scrollIntoView() doesn't work properly cos componentDidUpdate() has a default behavior that scrolls to the top overwriting the scrollIntoView().
To work-around it I wrapped the function calling scrollIntoView() in a setTimeout to ensure that happens afeterwards.
What I would like to do is to call a preventDefault() or any other more elegant solution but I can't find where to get the event triggering the 'scrollTop'
I looked through the Doc here: https://facebook.github.io/react/docs/react-component.html#componentdidupdate
and the params passed in this function are componentDidUpdate(prevProps, prevState) ,since there is no event I don't know how to call preventDefault()
I've followd this Docs: https://facebook.github.io/react/docs/refs-and-the-dom.html
And tried different approaches people suggested here: How can I scroll a div to be visible in ReactJS?
Nothing worked though
Here is my code if anyone has any tip for me, thanks
class PhotoContainer extends React.Component {
componentDidUpdate(){
setTimeout(() => {
this.focusDiv();
}, 500);
}
focusDiv(){
var scrolling = this.theDiv;
scrolling.scrollIntoView();
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>this is the div I'm trying to scroll to</div>
</div>
)
};
}
Ok it's been a while but I got it working in another project without the setTimeOut function so I wanted to answer this question.
Since Redux pass the new updates through props, I used the componentWillRecieveProps() method instead of componentDidUpdate() , this allowes you a better control over the updated properties and works as expected with the scrollIntoView() function.
class PhotoContainer extends React.Component {
componentWillReceiveProps(newProps) {
if (
this.props.navigation.sectionSelected !==
newProps.navigation.sectionSelected &&
newProps.navigation.sectionSelected !== ""
) {
this.focusDiv(newProps.navigation.sectionSelected);
}
}
focusDiv(section){
var scrolling = this[section]; //section would be 'theDiv' in this example
scrolling.scrollIntoView({ block: "start", behavior: "smooth" });//corrected typo
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>
this is the div I am trying to scroll to
</div>
</div>
)
};
}
I also struggled with scrolling to the bottom of a list in react that's responding to a change in a redux store and I happened upon this and a few other stackoverflow articles related to scrolling. In case you also land on this question as well there are a few ways this could be a problem. My scenario was that I wanted a 'loading' spinner screen while the list was rendering. Here are a few wrong ways to do this:
When loading = true, render spinner, otherwise render list.
{loading ?
<Spinner />
:
<List />
}
as stated above this doesn't work because the list you might want to scroll to the bottom of isn't rendered yet.
When loading set the display to block for the spinner and none for the list. When done loading, reverse the display.
<div style={{display: loading ? 'block' : 'none'>
<Spinner />
</div>
<div style={{display: loading ? 'none' : 'block'>
<List />
</div>
This doesn't work either since the list you want to scroll to the bottom of isn't actually being displayed likely when you call the scroll.
The better approach for the above scenario is to use a loading that acts as an overlay to the component. This way both the spinner and list are rendered and displayed, the scroll happens, and when the loading is complete, the spinner can be de-rendered or set to be invisible.
I'm attempting to do an animation with React and CSS classes. I have created a live demo, if you visit it and click the Start button you will see the text fade in and up one by one. This is the desired animation that I am after.
However, there seems to be issues of consistency when you hit Start multiple times and I cannot pinpoint why.
The Issue: Below is a recording of the issue, you can see the number 1 is not behaving as expected.
live demo
The process: Clicking Start will cancel any previous requestAnimationFrame' and will reset the state to it's initial form. It then calls the showSegments() function with a clean state that has no classNames attached to it.
This function then maps through the state adding a isActive to each segment in the state. We then render out the dom with a map and apply the new state.
This should create a smooth segmented animation as each class gets dropped one by one. However when i test this in Chrome (Version 56.0.2924.87 (64-bit)) and also on iOS, it is very inconsistent, sometimes it works perfectly, other times the first DOM element won't animate, it will just stay in up and visible it's completed transitioned state with "isActive".
I tried to replicate this issue in safari but it worked perfectly fine, I'm quite new to react so i am not sure if this is the best way to go about things, hopefully someone can offer some insight as to why this is behaving quite erratic!
/* MotionText.js */
import React, { Component } from 'react';
import shortid from 'shortid';
class MotionText extends Component {
constructor(props) {
super(props);
this.showSegments = this.showSegments.bind(this);
this.handleClickStart = this.handleClickStart.bind(this);
this.handleClickStop = this.handleClickStop.bind(this);
this.initialState = () => { return {
curIndex: 0,
textSegments: [
...'123456789123456789123456789123456789'
].map(segment => ({
segment,
id: shortid.generate(),
className: null
}))
}};
this.state = this.initialState();
}
handleClickStop() {
cancelAnimationFrame(this.rafId);
}
handleClickStart(){
cancelAnimationFrame(this.rafId);
this.setState(this.initialState(), () => {
this.rafId = requestAnimationFrame(this.showSegments);
});
}
showSegments() {
this.rafId = requestAnimationFrame(this.showSegments);
const newState = Object.assign({}, this.state);
newState.textSegments[this.state.curIndex].className = 'isActive';
this.setState(
{
...newState,
curIndex: this.state.curIndex + 1
},
() => {
if (this.state.curIndex >= this.state.textSegments.length) {
cancelAnimationFrame(this.rafId);
}
}
);
}
render(){
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
}
export default MotionText;
Thank you for your time, If there any questions please ask
WebpackBin Demo
Changing the method to something like this works
render(){
let d = new Date();
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={d.getMilliseconds() + obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
How this helps is that, the key becomes different than previously assigned key to first span being rendered. Any way by which you can make the key different than previous will help you have this animation. Otherwise React will not render it again and hence you will never see this in animation.
I have the below react component which is essentially a chat-box
render(){
const messages = this.props.messages;
return(
<div id="project_chat">
<h1>{this.props.project[0].project}</h1>
<div className="chat_room">
<div className="messages" ref="messages">
<Waypoint onEnter={this.activateWayPoint}/>
<ul>
{messages.map((message) => {
return(
<Message key={uuid.v4()} message={message}/>
)
})}
</ul>
</div>
<div className="chat_message_box">
<input type='text' onChange={this.handleChange} value={this.state.message} className="message_box" placeholder="enter message"/>
<button className="submit_message" onClick={this.handleSubmit}>Submit</button>
</div>
</div>
</div>
)
}
the problem i faced is the chat messages box starts at the topmost position of the container (scroll position starts at the top). I wanted the scroll position to be at the bottom like a normal chat room.
so i tried doing this:
componentDidMount(){
this.refs.messages.scrollTop = this.refs.messages.scrollHeight
}
this triggers AFTER the component gets mounted i.e - the message box scroll position initially starts at the top and forces its way to the bottom on render.
this is normally fine but i'm using a library called react-waypoint which would help me paginate chat messages. this gets triggered every time i'm at the top of the container.
the unhappy consequence is that because the message box starts at the top initially on mount, the waypoint always gets triggered on mount as well.
my question is whether i can force the message component to start at the bottom position as opposed to starting the top and going to the bottom at the beginning
I tried doing this
componentWillMount(){
this.refs.messages.scrollTop = this.refs.messages.scrollHeight
}
the problem is i dont have access to refs before the component mounts. is there any other way?
What you want is to avoid firing this.activateWayPoint before you've set scrollTop.
You can do this by setting a state variable waypointReady to false initially. Set it to true in componentDidMount.
Then, you can modify this.activateWayPoint to check this.state.waypointReady, and return immediately if it is false.
// inside component
getInitialState() {
return { waypointReady : false }
}
componentDidMount() {
this.refs.messages.scrollTop = this.refs.messages.scrollHeight;
this.setState({ waypointReady : true});
}
activateWayPoint() {
if (! this.state.waypointReady) return;
// Your code here!
// ...
}
You will probably have to bind this inside your render function:
// ...
<Waypoint onEnter={this.activateWayPoint.bind(this)}/>
// ...
Alternately, instead of performing the check inside this.activateWayPoint, you might perform the check inside render:
// ...
<Waypoint onEnter={
this.state.waypointReady ?
this.activateWayPoint :
null
}/>
// ...
This assumes that your component re-renders every time you setState.