Converting stateless to a class component react js - javascript

I have a component here that fills up a data for movies:
const MovieList = (props) => {
const movieItems = props.movies.map((movie) => {
return <MovieListItem key={movie._id} movie={movie} />
});
return(
<div className="container">
<h2 className="latest">MOVIE RESULTS</h2>
<div className="row">
{ movieItems }
</div>
</div>
);
};
Basically, I was planning to convert my next component where I am filling up my movieItems to a class component. This class has some helpers function along with a new function onClickImgMoreBtn whenever it was click it must console log something. But it doesnt work any more when I tried to convert it class component. Even my helper classes wont work either.
Here's my original stateless function:
import React from 'react';
const helper = require('../../helpers/helper_movies')
const MovieListItem = ({movie}) => {
return (
<div>
{helper.mappingComma(movie.genre)}
{helper.getExcerpt(movie.overview)}
<button className="btn btn-secondary float-right btn-sm" href={movie._id} onClick={this.onClickImgMoreBtn}>MORE</button>
</div>
);
};
export default MovieListItem;
And here's how i change it along with the new function:
import React, { Component } from 'react';
const helper = require('../../helpers/helper_movies');
class MovieListItem extends Component{
constructor(props){
super(props);
}
onClickImgMoreBtn(){
console.log('hi there!');
}
render(){
return (
<div>
{helper.mappingComma(this.props.genre)}
{helper.getExcerpt(this.props.overview)}
<button className="btn btn-secondary float-right btn-sm" href={this.props._id} onClick={this.onClickImgMoreBtn}>MORE</button>
</div>
);
}
}
export default MovieListItem;
Any idea what am i missing? did i properly converted it?

In your stateless component you destructure movie from the props and use movie.genre, movie._id and movie.overview and hence in the component you need to use
this.props.movie.genre, this.props.movie._id and this.props.movie.overview
render(){
return (
<div>
{helper.mappingComma(this.props.movie.genre)}
{helper.getExcerpt(this.props.movie.overview)}
<button className="btn btn-secondary float-right btn-sm" href={this.props.movie._id} onClick={this.onClickImgMoreBtn}>MORE</button>
</div>
);
}
or better, destructure movie form props in render
render(){
const { movie } = this.props;
return (
<div>
{helper.mappingComma(movie.genre)}
{helper.getExcerpt(movie.overview)}
<button className="btn btn-secondary float-right btn-sm" href={movie._id} onClick={this.onClickImgMoreBtn}>MORE</button>
</div>
);
}
Also since you are not doing anything in the constructor, you need to have the constructor defined.

Related

How to display the name of the clicked button in React?

They are working on the game of paper, stone, scissors. I would like to display its id in Result component after pressing one of the buttons. How can I do this?
App.js
import React, { Component } from "react";
import "./App.scss";
import SubmitInfo from "./SubmitInfo";
import ResultInfo from "./ResultInfo";
class App extends Component {
constructor(props) {
super(props);
this.test = this.test.bind(this);
}
test = id => {
//return <Result id={this.props.id}></Result>
console.log("test");
};
render() {
return (
<div>
<div className="board">
<div className="title_row">
<h1 className="title">Kamień, Papier, Nożyce</h1>
</div>
</div>
<div className="board">
<div className="submit_row">
<SubmitInfo id="papier" click={this.test} />
<SubmitInfo id="kamien" click={this.test} />
<SubmitInfo id="nozyce" click={this.test} />
<ResultInfo id={this.test} />
</div>
</div>
</div>
);
}
}
export default App;
SubmitInfo.js
Transfer to id props and click event, Then I render three buttons with different icons.
import React from "react";
import styles from "./submit.scss";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import {
faHandPaper,
faHandScissors,
faHandRock
} from "#fortawesome/free-solid-svg-icons";
import ResultInfo from "./ResultInfo";
function Submit({ id, click }) {
if (id === "nozyce") {
return (
<button className="submit" onClick={click}>
<FontAwesomeIcon className="icon" icon={faHandScissors} />
</button>
);
} else if (id === "papier") {
return (
<button className="submit" onClick={click}>
<FontAwesomeIcon className="icon" icon={faHandPaper} />
</button>
);
} else if (id === "kamien") {
return (
<button className="submit" onClick={click}>
<FontAwesomeIcon className="icon" icon={faHandRock} />
</button>
);
}
}
export default SubmitInfo;
ResultInfo.js
Here I would like to display the id of the button clicked.
import React, { Component } from "react";
class ResultInfo extends Component {
render(props) {
return (
<div>
{" "}
{this.props.id}
{console.log(this.props.id)}
</div>
//<div></div>
);
}
}
export default ResultInfo;
You should use state. Initialize it in the constructor
constructor(props) {
super(props);
this.state = {
id: null,
};
}
Your test function, should set the state with the id
test = id => {
this.setState({
id,
});
};
ResultInfo component should recieve the state id as an attribute
<ResultInfo id={this.state.id} />
And, the buttons on SubmitInfo component should call the click function, with the id as a parameter
<button className='submit' onClick={() => click(id)}>Rock</button>
David's explanation is correct.
I was working on a codesandbox example on parallel, figured it'd help.
You have some additional errors, like exporting default SubmitInfo instead of Submit, and you can use conditional rendering of ResultInfo component.

React sibling communication with ONLY access to parent

I am trying to create something similar to React Bootstrap's dropdown component. My starting skeleton is something like the following:
import React from 'react';
const DropDown = props => {
return <div className="dropdown-container">{props.children}</div>;
};
const DropDownToggle = props => {
return <div className="dropdown-toggle">{props.children}</div>;
};
const DropDownContent = props => {
return <div className="dropdown-content">{props.children}</div>;
};
export { DropDown, DropDownToggle, DropDownContent };
These components would be used like this:
<DropDown>
<DropDownToggle>
{/*
The content inside here should be customizable so the user of
these components can specify whatever they want for the toggle
*/}
<button type="button">
my button
</button>
</DropDownToggle>
<DropDownContent>
{/*
The content inside here should be customizable so the user of
these components can specify whatever they want for the content of
the dropdown
*/}
<ContentComponent/>
</DropDownContent>
</DropDown>
Is there a way I can communicate between the two children components (DropDownContent and DropDownToggle)? I have access to the parent component and it just receives and displays the children so far, but I would like to somehow communicate between the children so that the user can click on the toggle to open/close the content. I don't want to use redux.
Thank you in advance!
EDIT
I ended up going with the method that #Train suggested in his/her comment below. I was originally hoping for the ability to nest components manually, but what was most important to me was having the state be self-contained in the parent component. Being able to define the toggle button's HTML as well as the content's HTML was also a requirement. My final implementation allows for both of these things and looks something like this:
import React from 'react';
import PropTypes from 'prop-types';
export class Dropdown extends React.Component {
state = {
isOpen: false,
};
onDropDownToggleClick = () => {
this.setState({ isOpen: !this.state.isOpen });
};
render() {
let contentClasses = 'dropdown-content';
if (this.state.isOpen) {
contentClasses += ' show';
}
return (
<div className="dropdown-container">
<div className="dropdown-toggle" onClick={this.onDropDownToggleClick}>
{this.props.toggle}
</div>
<div className={contentClasses}>{this.props.content}</div>
</div>
);
}
}
Dropdown.propTypes = {
toggle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
content: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
.isRequired,
};
export default Dropdown;
to use it:
const dropDownToggle = (
<button type="button">
Dropdown
</button>
);
const dropDownContent = 'content';
<DropDown
toggle={dropDownToggle}
content={dropDownContent}
/>
For something like toggling content you can use composition instead of inheritance to pass data around.
From the example of Facebook
This is done with props.children property.
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
In the render() I am rendering the Dialog component and passing in the props.
the props are .children and the custom props title, message
This lets us pass child elements directly into the output we can even add components from other classes as I did with the SignUpDialog.
Did you have something like this in mind?
const actionTypes = {
TOGGLE: "TOGGLE"
};
const notRedux = {
actionHandlers: Object.keys(actionTypes).reduce(
(acc, val) => ({ [val]: [], ...acc }),
{}
),
dispatchAction(actionType, data) {
this.actionHandlers[actionType].forEach(handler => handler(data));
},
onAction(actionType, actionHandler) {
this.actionHandlers[actionType].push(actionHandler);
}
};
const DropDown = ({ children }) => {
return <div className="dropdown-container">{children}</div>;
};
const DropDownToggle = () => {
const onClick = () =>
notRedux.dispatchAction(actionTypes.TOGGLE, "oh hi Mark");
return (
<div className="dropdown-toggle">
<button type="button" onClick={onClick}>
my button
</button>
</div>
);
};
const DropDownContent = props => {
notRedux.onAction(actionTypes.TOGGLE, data =>
alert(`DropDownToggle said ${data} //DropDownContent`)
);
return <div className="dropdown-content">{props.children}</div>;
};
const App = () => (
<DropDown>
<DropDownToggle></DropDownToggle>
<DropDownContent>
<span>Content goes here</span>
</DropDownContent>
</DropDown>
);
ReactDOM.render(<App />, document.getElementById("app"));
<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="app"></app>

Change react-modal data dynamically

I have a Parent component, App.js and a Child component, MealModal.js. When a user click on a specific meal card, it raises a modal that should display further information about the meal.
Hence I try to find a way to dynamically change the modals's data, depending on which meal card is clicked
I have tried to pass the id of the meal to the onClick={this.openModal} function and set the state of modalId is the function. But I got the following error:
Warning: Cannot update during an existing state transition (such as
within render or another component's constructor). Render methods
should be a pure function of props and state; constructor side-effects
are an anti-pattern, but can be moved to 'componentWillMount'.
Here are my components so far:
App.js:
import React from 'react';
import MealCard from './MealCard';
import MealsMap from './MealsMap';
import MealsFilters from './MealsFilters';
import MealModal from './MealModal';
export default class App extends React.Component {
constructor() {
super();
this.state = {
modalIsOpen: false,
modalId: 0
}
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
};
openModal() {
this.setState({modalIsOpen: true});
};
closeModal() {
this.setState({modalIsOpen: false});
};
render() {
return (
<div>
<MealsFilters/>
<div className="app-wrapper" style={{display: 'flex'}}>
<div className="container">
<div className="row">
{[...Array(20)].map((x, i) =>
<div className="col-sm-6 col-xs-12 " key={i} onClick={this.openModal}>
<MealCard />
</div>
)}
</div>
</div>
<MealsMap/>
</div>
<MealModal modalIsOpen={this.state.modalIsOpen} closeModal={this.closeModal} modalId={this.state.modalId}/>
</div>
);
}
}
MealModal.js
import React from 'react';
import Modal from 'react-modal';
const customStyles = {
content : {
}
};
Modal.setAppElement('#app')
export default class MealModal extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Modal
isOpen={this.props.modalIsOpen}
onRequestClose={this.props.closeModal}
style={customStyles}
contentLabel="Meal Modal"
>
<div className="modal-wrapper">
<div className="container text-center">
<h1>Hello</h1>
<h2>ID of this modal is {this.props.modalId}</h2>
<h3>This is an awesome modal.</h3>
<button onClick={this.props.closeModal}>close</button>
</div>
</div>
</Modal>
)
}
}
Any idea on how I could do this ?
Ok so I found the solution:
First, I changed onClick={this.openModal} in the parent comoponent to onClick= () => {this.openModal}
Second, I add the id as a parameter:
onClick= () => {this.openModal(i)}
Finally: update the openModal function:
openModal(modalId) {
this.setState({modalIsOpen: true,
modalId});
};
And it works.
openModal(modalId) {
this.setState({
modalId,
modalIsOpen: true
});
};
and modify the call function as
<div className="col-sm-6 col-xs-12" key={i} onClick={() => this.openModal(x) } >
<MealCard/>
</div>

React Help Needed - Components not updating when index increases

I cannot get the component displayed to update when the index increases. I am able to console the proper component now but because the onClick is below the component that needs to update, it isn't changing. Can someone help me fix my code? i think I am close but cannot figure it out for the life of me.
This sign up page is where I would like to update the component. Essentially I want to display each component in the array once the next button is clicked. Currently the function console logs everything as I want it to, it's just a matter of getting it to appear in the
it is returning an error "cannot read property 'count' of null":
import React from 'react';
import Q1Name from './questions/Q1Name';
import Q2Birthday from './questions/Q2Birthday';
import Q3City from './questions/Q3City';
import Q4YouReady from './questions/Q4YouReady';
import Q5Setting from './questions/Q5Setting';
import Q6Length from './questions/Q6Length';
import Q7Email from './questions/Q7Email';
class SignUpPage extends React.Component {
constructor(props) {
super(props);
this.state = {
i: 0
}
}
_handleClick() {
const components = [Q1Name, Q2Birthday, Q3City, Q4YouReady, Q5Setting, Q6Length, Q7Email];
if(this.state.i < components.length) this.setState({ i : this.state.i + 1});
}
// handleIncrement() {
// this.setState({ count: this.state.count + 1});
// }}
render() {
const components = [Q1Name, Q2Birthday, Q3City, Q4YouReady, Q5Setting, Q6Length, Q7Email];
const componentsToRender = components.map((Component, i) => (
<Component key={i} />
));
return (
<div className = "container-fluid signup-page">
<div className = "question-box">
{componentsToRender[this.state.i]}
<button type="submit" className="btn btn-custom btn-lg" onClick={() => this._handleClick}>Next Question!</button>
</div>
</div>
);
}
}
export default SignUpPage;
There are a few component types I am bringing in, age, birthday, email, and a few button clicks, etc.
import React from 'react';
class Q1Name extends React.Component {
handleSubmit(event) {
event.preventDefault();
this.props.onNext();
}
render() {
return (
<div className="questions q1" style={this.props.style}>
<h1 id="question-h1">What is your name?</h1>
<form>
<div className="form-group">
<input type="name" className="form-control text-form custom-form" id="nameInput" aria-describedby="name" placeholder="" />
</div>
{/* <button type="submit" className="btn btn-custom btn-lg" onSubmit={this.handleSubmit}>Next Question!</button> */}
</form>
</div>
);
}
}
export default Q1Name;
Here is an example of the button option component:
import React from 'react';
class Q5Setting extends React.Component {
render() {
return (
<div className="questions">
<h1 id="question-h1">What is your ideal setting?</h1>
<button type="button" className="btn btn-custom-select btn-lg">Take me to the beach!</button>
<button type="button" className="btn btn-custom-select btn-lg">Anywhere outdoors!</button>
<button type="button" className="btn btn-custom-select btn-lg">All about the city!</button>
</div>
);
}
}
export default Q5Setting;
Any help in figuring this out would be greatly appreciated!!
In your constructor initialise state
constructor(props) {
super(props)
this.state = { i: 0 }
}
Write helper method handleClick
_handleClick() {
if(this.state.i < components.length) this.setState({ i : this.state.i + 1});
}
Now reference componentsToRender using i in state
`componentsToRender[this.state.i]
Don't forget to call your helper function on click.
onClick = {() => this._handleClick()}
The idea is your app will only re-render when your state object changes. Follow that rule for your components you wish to re-erender on the fry.

Reactjs controlling state in parent from grand child

I have at my top level:
import React from 'react';
import JobList from './JobList';
import RightPanel from './RightPanel';
import JobStore from '../../stores/JobStore';
import LoadJobsScreen from '../../actions/jobs-screen/LoadJobsScreen';
import Modal from '../global/Modal';
export default class JobScreen extends React.Component {
static contextTypes = {
executeAction: React.PropTypes.func.isRequired
};
componentWillMount() {
this.toggleModal = this.toggleModal.bind(this);
this.state = {open: false}
this.context.executeAction(LoadJobsScreen, this);
}
toggleModal() {
this.setState({
open: !this.state.open
});
console.log(this.state.open);
}
render() {
return (
<div className="jobs-screen">
<div className="col-xs-12 col-sm-10 job-list"><JobList /></div>
<div className="col-xs-12 col-sm-2 panel-container">
<div className="right-panel pull-right"><RightPanel /></div>
</div>
<Modal open={this.state.open} toggleModal={this.toggleModal} />
</div>
);
}
}
Modal is:
import React from 'react';
class Modal extends React.Component {
constructor() {
super();
}
render() {
let open = this.props.open;
return (
<div className={'modal fade'+(open ? '' : ' hide')} tabindex="-1" role="dialog">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 className="modal-title">{this.props.title}</h4>
</div>
<div className="modal-body">
{this.props.children}
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" className="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
)
}
}
export default Modal;
But I want to open and close it (as well as send data to it later) from within a deeper component:
import React from 'react';
import UrgencyToggle from './UrgencyToggle';
import ApproveButton from './ApproveButton';
import ShippingTable from './ShippingTable';
import DropdownButtonList from '../global/DropdownButtonList';
export default class Job extends React.Component {
constructor(props) {
super(props);
}
setUrgency(urgency) {
actionContext.dispatch('SET_JOB_URGENCY', {
data: urgency
})
};
render() {
return ( <
span className = "name" > < img src = "/images/system-icons/pencil.png"
onClick = {
this.toggleModal
}
width = "13" / > < /span>
)
}
};
Obviously this doesn't work because toggleModal is all the way up in JobScreen. How can I execute the function in the grandparent from this depth?
If your JobScreen, JobList, Joband Modal components are designed to be tightly coupled, i.e not meant to be separated from each other in future, you could use the JobScreen as a High Order Component to store the state of your modal and passing down the tree as prop a callback function to update this state (I simplified a bit and made some assumptions on missing components) :
export default class JobScreen extends React.Component {
constructor(props) {
super(props);
this.displayName = 'JobScreen'
this.state = {
modalOpened: false,
modalTitle: "",
}
}
componentWillMount() {
this.context.executeAction(LoadJobsScreen, this);
}
toggleModal() {
this.setState({
modalOpened: !this.state.modalOpened
});
}
editModalTitle(title) {
this.setState({
modalTitle: title
})
}
render() {
return (
<div className="jobs-screen">
<div className="col-xs-12 col-sm-10 job-list">
<JobList
toggleModal={() => this.toggleModal() /* auto binding with arrow func */}
editModalTitle={(title) => this.editModalTitle(title)} />
</div>
<Modal
open={this.state.modalOpened}
title={this.state.modalTitle}/>
</div>
);
}
}
const JobList = (props) => {
const jobs = [1,2,3]
return (
<ul>
{jobs.map(key => (
<li key={key}>
<Job
toggleModal={props.toggleModal}
editModalTitle={props.editModalTitle}/>
</li>
))}
</ul>
);
}
const Job = (props) => {
return (
<span className="name">
<img
src="/images/system-icons/pencil.png"
width="13"
onClick={(e) => {
props.toggleModal(e)
props.editModalTitle("new title") //not very efficient here cause we're updating state twice instead of once, but it's just for the sake of the example
}}/>
</span>
);
}
I deliberately not mentions how to modify the modal children this way cause it's an absolute anti-pattern. So, you should definitively look at something like Redux which provides a way to manage the state of your application and dispatch action from wherever you want to update in a 'one way data binding' way. I've the impression you're trying to bypass the React internal mechanisms by using context as an action dispatcher. So, Redux (or another Flux library) will be your best bet here.

Categories