How to trigger a function from another component's onclick event - reactjs

So I'm new in ReactJS and there's a cool company offering a junior react developer position if I solve their test project correctly. In the project, I have to draw four svg components and handle some simple events with only one user interaction which is a button click event. So everything seems quite simple but the thing is, I'm stucked in the middle of the project.
I want to give a shake animation to one of the components with a click event in the button component. When Button is clicked, the Tree should shake for 3 seconds and one other action will be triggered after that. I prepared a css animation and I want to change the class of the Tree component every time when the Button component clicked. Could not find out how to do so.
So here is my button component
class ShakeButton extends React.Component {
this.props.onClick(this.props.activeClass==='shake')
render() {
return (
<g id="shakeButton" transform="scale(0.15,0.15), translate(3000,-1000)" onClick={this.handleClick}>
//a good amount of svg content here
</g>
)
}
}
export default ShakeButton
and here my Tree component (which is gonna change its class name for css animation)
import React, { Components } from "react";
import "./cssTree.css";
class AppleTree extends React.Component{
// state = {
// activeClass: ''
// } n
// handleClick = () => this.props.onClick(this.props.activeClass==='shake')
render() {
return (
<g className="" id="appleTree" >
<g transform="translate(-1000 -1300) scale(1.5)">
//again, some crowded svg content here
</g>
</g>
)
}
}
export default AppleTree
I collect all of my components in a component named <Canvas /> and render it inside the App.js

You could try lift the onClick method and state up, meaning get it in your parent component (App I suppose), then pass it down to child components.
class App extends React.Component{
constructor(props) {
super(props);
this.state({
className: ''
});
}
handleClick = () => {
this.setState({ className: 'shake' });
}
render (
return (
// all your components
<Button clickHandler={this.handleClick} />
<AppleTree class={this.state.className} />
)
)
}
// Button component: Stateless if it doesn't need it's own state
const Button = ({ clickHandler }) => {
return (
<g id="shakeButton" transform="scale(0.15,0.15), translate(3000,-1000)" onClick={() => clickHandler()}>
//a good amount of svg content here
</g>
)
}
// Stateless if it doesn't need its own state
const AppleTree = ({ class }) => {
return (
<g className={class} id="appleTree" >
<g transform="translate(-1000 -1300) scale(1.5)">
//again, some crowded svg content here
</g>
</g>
)
}

Related

How to stop React-spring from running an animation on re-render

I am using react-spring to animate my components. But I want to only run the animation when mounting then the leaving animation when unmounting. But my animation runs everytime a component re-renders. every prop change causes the animation to run again which is not how I expect it to work. thanks for the help in advance.
/* eslint-disable react/jsx-props-no-spreading */
import * as React from 'react'
import { Transition, animated, config } from 'react-spring/renderprops'
class Animation extends React.Component {
constructor(props) {
super(props)
this.shouldAnimate = props.shouldAnimate
}
render() {
const Container = animated(this.props.container)
return (
<Transition
items={this.props.items}
native
initial={this.props.initial}
from={this.props.from}
leave={this.props.leave}
enter={this.props.enter}
config={config.gentle}
>
{(visible) =>
visible &&
((styles) => (
<Container
style={styles}
{...this.props.containerProps}
>
{this.props.children}
</Container>
))
}
</Transition>
)
}
}
export default Animation
I have found a solution to my very question after carefully looking at my component and logging the life-cycle method calls. I found out that each prop change was causing creation of a new container component and therefore causing the component to unmount and remount which in turn caused the animation to play. and the solution was easy after realizing this. I just changed my file to this and now it works just fine.
/* eslint-disable react/jsx-props-no-spreading */
import * as React from 'react'
import { Transition, animated } from 'react-spring/renderprops'
class Animation extends React.PureComponent {
constructor(props) {
super(props)
this.shouldAnimate = props.shouldAnimate
this.container = animated(this.props.container)
}
render() {
const Container = this.container
return (
<Transition
items={this.props.items}
native
initial={this.props.initial}
from={this.props.from}
update={this.props.update}
leave={this.props.leave}
enter={this.props.enter}
>
{(visible) =>
visible &&
((styles) => (
<Container
style={styles}
{...this.props.containerProps}
>
{this.props.children}
</Container>
))
}
</Transition>
)
}
}
export default Animation

React - Proper way to render dynamic content?

I want to make a modal view has dynamic content by injecting a component to it.
class RootView extends Component {
state = {
modalBody: null
}
setModalBody = (body) => {
this.setState({modalBody: body})
}
render() {
return(<ContextProvider value={this.setModalBody}><Modal>{this.state.modalBody}</Modal></ContextProvider>)
}
}
Then inside any children view i use setState to change parent modalBody
The modalBody can be setted on each route, which means the modalBody can be input list, selection list or text only. So the modalBody must have its state for controlling these inputs.
By this way, it renders ok, but the dynamic content couldn't be updated after state changed. The parent's dynamic content couldn't receive the ChildView new state, i have to setModalBody again and again after it rerendered.
For example, if input in modalBody has changed, the parent couldn't be updated.
class ChildView extends Component {
state = {
inputValue: null
}
handleChange = (e) => {
this.setState({inputValue: e.target.value})
}
setModalBody(body) {
this.props.context.setModalBody(<input value={this.state.inputValue} onChange={this.handleChange} />)
}
render() {
return(<Modal>{this.state.modalBody}</Modal>)
}
}
Full code: https://codesandbox.io/s/lp5p20mx1m
Any proper way to render dynamic content to parent?
I'm not sure why you'd need to create a parent Modal component, when you can make the Modal a simple reusable child component.
See here for a detailed explanation on how to achieve a stateful parent that controls a child modal.
However, if you MUST have a parent Modal component, then you can create a render prop to pass down props to be used by its children.
Working example:
components/Modal.js (parent component -- this has a lot of smaller components that were separated for reusability and ease of understanding -- they're basically simple divs with some styles attached -- see notes below)
import React, { Fragment, Component } from "react";
import PropTypes from "prop-types";
import BackgroundOverlay from "../BackgroundOverlay"; // grey background
import ClickHandler from "../ClickHandler"; // handles clicks outside of the modal
import Container from "../Container"; // contains the modal and background
import Content from "../Content"; // renders the "children" placed inside of <Modal>...</Modal>
import ModalContainer from "../ModalContainer"; // places the modal in the center of the page
class Modal extends Component {
state = { isOpen: false };
handleOpenModal = () => {
this.setState({ isOpen: true });
};
handleCloseModal = () => {
this.setState({ isOpen: false });
};
// this is a ternary operator (shorthand for "if/else" -- if cond ? then : else)
// below can be read like: if isOpen is true, then render the modal,
// else render whatever the child component is returning (in this case,
// initially returning an "Open Modal" button)
render = () =>
this.state.isOpen ? (
<Container>
<BackgroundOverlay />
<ModalContainer>
<ClickHandler
isOpen={this.state.isOpen}
closeModal={this.handleCloseModal}
>
<Content>
{this.props.children({
isOpen: this.state.isOpen,
onCloseModal: this.handleCloseModal,
onOpenModal: this.handleOpenModal
})}
</Content>
</ClickHandler>
</ModalContainer>
</Container>
) : (
<Fragment>
{this.props.children({
isOpen: this.state.isOpen,
onCloseModal: this.handleCloseModal,
onOpenModal: this.handleOpenModal
})}
</Fragment>
);
}
// these proptype declarations are to ensure that passed down props are
// consistent and are defined as expected
Modal.propTypes = {
children: PropTypes.func.isRequired // children must be a function
};
export default Modal;
components/Example.js (child component accepting isOpen, onCloseModal and onOpenModal from the parent -- with this approach, as you'll notice, there's duplicate isOpen logic. While this approach gives you full control over the parent, it's repetitive. However, you can simplify your components by moving the "Open Modal" button logic to the parent, and passing in a prop like <Modal btnTitle="Open Modal"> to make it somewhat flexible, BUT you'll still lose some control of what's being initially rendered when isOpen is false.)
import React, { Fragment } from "react";
import Modal from "../Modal";
import "./styles.css";
const Example = () => (
<div className="example">
<h2>Parent Modal Example</h2>
<Modal>
{({ isOpen, onCloseModal, onOpenModal }) =>
isOpen ? (
<Fragment>
<h1 className="title">Hello!</h1>
<p className="subtitle">There are two ways to close this modal</p>
<ul>
<li>Click outside of this modal in the grey overlay area.</li>
<li>Click the close button below.</li>
</ul>
<button
className="uk-button uk-button-danger uk-button-small"
onClick={onCloseModal}
>
Close
</button>
</Fragment>
) : (
<button
className="uk-button uk-button-primary uk-button-small"
onClick={onOpenModal}
>
Open Modal
</button>
)
}
</Modal>
</div>
);
export default Example;

How to create a new ref for each component instance

How to create a ref for each instance of a component
I've extracted some code into it's own component. The component is a PlayWhenVisible animation component that plays/stops the animation depending on whether the element is in view.
I'm creating a ref inside the component constructor but since I'm getting some lag when using 2 instances of the component I'm wondering if I should create the refs outside the component and pass them in as props or whether there's a way to create a new instance for each compoenent instance.
import VisibilitySensor from "react-visibility-sensor";
class PlayWhenVisible extends React.Component {
constructor(props) {
super(props);
this.animation = React.createRef();
this.anim = null;
}
render() {
return (
<VisibilitySensor
scrollCheck
scrollThrottle={100}
intervalDelay={8000}
containment={this.props.containment}
onChange={this.onChange}
minTopValue={this.props.minTopValue}
partialVisibility={this.props.partialVisibility}
offset={this.props.offset}
>
{({ isVisible }) => {
isVisible ? this.anim.play() : this.anim && this.anim.stop();
return (
// <div style={style}>
<i ref={this.animation} id="animation" className={this.props.class} />
);
}}
</VisibilitySensor>
);
}
}
The issue was caused by the VisibilityChecker component which was overflowing the container and causing it to be erratic when firing.

Open a <Modal> by clicking on an element rendered in another component

I am using a group of Semantic UI <Item> components to list a bunch of products. I want to be able to edit the the details of a product when the <Item> is clicked, and I thought the best way to achieve this would be using a <Modal> component.
I want to have everything split into reusable components where possible.
(Note: I've purposefully left out some of the import statements to keep things easy to read.)
App.js
import { ProductList } from 'components';
const App = () => (
<Segment>
<Item.Group divided>
<ProductList/>
</Item.Group>
</Segment>
)
export default App;
components/ProductList.js
import { ProductListItem } from '../ProductListItem';
export default class ProductList extends Component {
constructor() {
super()
this.state = { contents: [] }
}
componentDidMount() {
var myRequest = new Request('http://localhost:3000/contents.json');
let contents = [];
fetch(myRequest)
.then(response => response.json())
.then(data => {
this.setState({ contents: data.contents });
});
this.setState({ contents: contents });
}
render() {
return (
this.state.contents.map(content => {
return (
<ProductListItem
prod_id={content.prod_id}
prod_description={content.prod_description}
category_description={content.category_description}
/>
);
})
)
}
}
components/ProductListItem.js
export default class ProductListItem extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Item key={`product-${this.props.prod_id}`} as='a'>
<Item.Content>
<Item.Header>{this.props.prod_description}</Item.Header>
<Item.Description>
<p>{this.props.prod_description}</p>
</Item.Description>
</Item.Content>
</Item>
)
}
}
All of this works nicely and the list of products displays as it should.
I've also created a basic modal component using one of the examples in the Modal docs:
components/ModalExampleControlled.js
export default class ModalExampleControlled extends Component {
state = { modalOpen: false }
handleOpen = () => this.setState({ modalOpen: true })
handleClose = () => this.setState({ modalOpen: false })
render() {
return (
<Modal
trigger={<Button onClick={this.handleOpen}>Show Modal</Button>}
open={this.state.modalOpen}
onClose={this.handleClose}
size='small'
>
<Header icon='browser' content='Cookies policy' />
<Modal.Content>
<h3>This website uses cookies etc ...</h3>
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={this.handleClose}>Got it</Button>
</Modal.Actions>
</Modal>
)
}
}
So this will create a button that reads Got it wherever <ModalExampleControlled /> is rendered, and the button causes the modal to appear - great.
How do I instead get the modal to appear when one of the <Item> elements in the product list is clicked (thus getting rid of the button)?
Thanks so much for your time.
Chris
Your problem is that currently the modal manages its own state internally. As long as this is the case and no other component has access to that state, you can not trigger the modal component from outside.
There are various ways to solve this. The best way depends on how your app is set up. It sounds like the best way to go is to replace the internal modal state with a prop that is passed to the modal from a higher order component that also passes open/close functions to the relevant children:
// Modal.js
export default class ModalExampleControlled extends Component {
render() {
return (
{ this.props.open ?
<Modal
open={this.props.open}
onClose={this.props.handleClose}
size='small'
>
<Header icon='browser' content='Cookies policy' />
<Modal.Content>
<h3>This website uses cookies etc ...</h3>
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={this.props.handleClose}>Got it</Button>
</Modal.Actions>
</Modal>
: null }
)
}
}
// App.js
import { ProductList } from 'components';
class App extends Component {
handleOpen = () => this.setState({ open: true })
handleClose = () => this.setState({ open: false })
render(){
return(
<Segment>
<Item.Group divided>
<ProductList/>
</Item.Group>
<Modal open={this.state.open} closeModal={() => this.handleClose()}}
</Segment>
)
}
}
export default App;
Keep in mind that this code is rather exemplary and not finished. The basic idea is: You need to give control to the highest parent component that is above all other components that need access to it. This way you can pass the open/close functions to the children where needed and control the modal state.
This can get unwieldy if there is a lot of this passing. If your app gets very complex it will become a matter of state management. When there is a lot going on a pattern like Redux might help to manage changing states (e.g. modals) from everywhere. In your case this might be finde, though.

How to render a redux connected component in a modal

I am trying to add authentication to my react redux app, and I want to use modals for the login and signup pages. I have a LoginForm and a SignupForm which are static components, and I have LoginPage and SignupPage, which are my container components for the forms.
I am currently able to render the container components using login and signup routes, but I want switch to using modals.
Please how do I do this using a modal, say, react-materialize modal or normal materialize css modal?
Thanks for the help.
Revisiting this answer, I would say you need to have two components able to communicate. This is the plain React way to do this. Notice there is one App component with two sibling components. By passing a props value to the Modal component and a props callback to its sibling, we can get the modal to render or not render.
class Modal extends React.Component {
render() {
return this.props.modalShow ? (
<div className="modal">
Modal loaded
</div>
) : null;
};
};
class Sibling extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.openModal();
}
render() {
return (
<div>
I am a sibling element.
<button onClick={this.handleClick}>Show modal</button>
</div>
)
};
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
modalShow: false,
};
this.openModal = this.openModal.bind(this);
};
openModal() {
this.setState({ modalShow: true });
}
render() {
return (
<div>
<Modal modalShow={this.state.modalShow} />
<Sibling openModal={this.openModal}/>
</div>
)
};
};
ReactDOM.render(
<App />,
document.getElementById('app')
);
From here, using Redux is much simpler since the state is "global" already. But the idea is the same. Instead of having a parent control the state, just use your reducers and actions. I hope this helps.

Resources