My showroom component is as follows :
export default class Showrooms extends React.Component {
constructor(props){
super(props);
this.state = {
blockView: true
};
}
handleChangeView(view){
console.log(view);
this.setState({blockView: view});
}
render(){
const language = this.props.language;
return (
<div className="col-lg-10 col-md-9 col-sm-9 col-xs-12 text-center">
<div className="lists">
<div className="listsTitle">{language.portal.introPageTitle}</div>
<ViewBar handleChangeView={this.handleChangeView.bind(this)}/>
<div className="allLists">
<div className="view">
{this.props.allLists.map( (list, i) => <View key={i} list={list} language={language} blockView={this.state.blockView}/>)}
<div className="clearfix"/>
</div>
</div>
<div className="clearfix"/>
</div>
</div>
);
}
}
and my viewBar component is as follows :
export default class ViewBar extends React.Component {
constructor(props){
super(props);
this.state = {
blockView: true
};
}
setBlockView(event){
event.preventDefault();
this.setState({blockView: true}, this.props.handleChangeView(this.state.blockView));
}
setListView(event){
event.preventDefault();
this.setState({blockView: false}, this.props.handleChangeView(this.state.blockView));
}
render(){
let blockViewAddBorder = this.state.blockView ? "activeView" : "";
let listViewAddBorder = !this.state.blockView ? "activeView" : "";
return (
<div className="viewBar">
<Link to="" onClick={this.setListView.bind(this)} className={`listViewIcon ${listViewAddBorder}`}>
<FontAwesome name="list" className="portalFaIcon"/>
</Link>
<Link to="" onClick={this.setBlockView.bind(this)} className={blockViewAddBorder}>
<FontAwesome name="th-large" className="portalFaIcon"/>
</Link>
</div>
)
}
}
In viewBar component I have two onClick functions where I update the state and the I call an function from showroom component to update the state.
Depending on that state I change the way how I display the content.
But the problem is, when the function setListView is called first time, the state doesn't change to false. When I second time call setListView then it sets the state to false.
this.props.handleChangeView function is an callback function, and it should be called after the state is updated.
Any advice?
Second argument in setState should be function
this.setState({ blockView: true }, () => {
this.props.handleChangeView(this.state.blockView);
})
in your example, you pass result to setState from handleChangeView
this.setState({
blockView: true
}, this.props.handleChangeView(this.state.blockView));
handleChangeView returns nothing, it means that you pass to setState undefined
this.setState({
blockView: true
}, undefined);
so you don't call handleChangeView after setState
Related
How to update state of one component in another in react class component.
I have two class in reacts.
MyComponent and MyContainer.
export default class MyContainer extends BaseComponent{
constructor(props: any) {
super(props, {
status : false,
nameValue :"",
contentValue : ""
});
}
componentDidMount = () => {
console.log(this.state.status);
};
save = () => {
console.log("Hello I am Save");
let obj: object = {
nameValue: this.state.nameValue, // here I am getting empty string
templateValue: this.state.contentValue
};
// API Call
};
render() {
return (
<div>
<MyComponent
nameValue = {this.state.nameValue}
contentValue = {this.state.contentValue}
></MyComponent>
<div >
<button type="button" onClick={this.save} >Save</button>
</div>
</div>
);
}
}
MyComponent
export default class MyComponent extends BaseComponent{
constructor(props: any) {
super(props, {});
this.state = {
nameValue : props.nameValue ? props.nameValue : "",
contentValue : props.contentValue ? props.contentValue : "",
status : false
}
}
componentDidMount = () => {
console.log("MOUNTING");
};
fieldChange = (id:String, value : String) =>{
if(id === "content"){
this.setState({nameValue:value});
}else{
this.setState({contentValue:value});
}
}
render() {
return (
<div>
<div className="form-group">
<input id="name" onChange={(e) => {this.fieldChange(e)}}></input>
<input id = "content" onChange={(e) => {this.fieldChange(e)}} ></input>
</div>
</div>
);
}
}
In MyComponent I have placed two input field where on change I am changing the state.
Save button I have in MyContainer. In save button I am not able to read the value of MyComponent. What is the best way to achieve that.
You should be updating your state in MyContainer for save to have visibility of the state changes. Each component gets its own state, which makes MyComponent's state unique to that of MyContainer. What you should be doing is keeping the state in your parent/container component, and then passing it down as props (rather than duplicating it in your child). To do this, move fieldChange up to the MyContainer function, and remove the duplicate nameValue and contentValue state within MyComponent. See code commennts for further details:
export default class MyContainer extends BaseComponent{
...
fieldChange = (id:String, value : String) =>{
if(id === "content"){
this.setState({nameValue: value});
} else {
this.setState({contentValue: value});
}
}
render() {
return (
<div>
<MyComponent
nameValue={this.state.nameValue}
contentValue={this.state.contentValue}
onFieldChange={this.fieldChange} /* <---- Pass the function down to `MyComponent` */
/>
...
</div>
);
}
}
Then in MyComponent, call this.props.onFieldChange:
export default class MyComponent extends BaseComponent{
// !! this constructor can be removed as no state is being initialized anymore !!
constructor(props: any) {
super(props);
// removed state as we're using the state from `MyContainer`
}
componentDidMount = () => {
console.log("MOUNTING");
};
render() {
return (
<div>
<div className="form-group">
<input id="name" onChange={(e) => {this.props.fieldChange(e)}} /> /* <--- Change to `this.props.fieldChange()`. `<input />` is a self-closing tag.
<input id = "content" onChange={(e) => {this.props.fieldChange(e)}} />
</div>
</div>
);
}
}
Some additional notes:
If your component doesn't use this.props.children, then you should call it as <MyComponent ... props ... /> not <MyComponent ... props ...></MyComponent>
Your if-statement in your fieldChange looks reversed and should be checking if(id === "name"). I'm assuming this is an error in your question.
You're only passing one argument to fieldChange in your example code. I'm again assuming this in an error in your question.
I'm trying to create a modal that asks users if they're an individual or organization, and then shows a sign up modal specific to that type of user. This is what I have so far:
parent:
this.state = {
showInd: false,
showOrg: false,
};
changeInd = () => {
this.setState({
showInd: !this.state.showInd
});
this.props.onClose(); //this closes the initial modal
}
//exact same syntax for changeOrg
render(){
return(
<div onClick={this.changeInd}>
<FontAwesomeIcon icon={faUser} className="fa-7x icon"/>
<span>individual</span>
</div>
<div onClick={this.changeOrg}>
<FontAwesomeIcon icon={faUsers} className="fa-7x icon"/>
<span>organization</span>
</div>
<SignUpInd show={this.state.showInd} />
<SignUpOrg show={this.state.showOrg} />
)}
and the child:
render(){
if (this.props.show){
return(
<various sign up html>
)}
}
The parent component is re-rendering when the state changes, but the child component is not, even though the props are changing. I've tried using componentDidUpdate, but that is also never triggered when the props change here.
What could I be doing wrong?
EDIT: So I've realized that if I comment out the line that closes the initial modal with a callback function, the signUpInd modal will render properly. Why can I not do both?
this works:
import React from "react";
import SignUpInd from "./SignUpInd";
import SignUpOrg from "./SignUpOrg";
import "./styles.css";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showInd: false,
showOrg: false
};
}
showInd = () => {
this.setState((state) => ({ showInd: !state.showInd }));
};
showOrg = () => {
this.setState((state) => ({ showOrg: !state.showOrg }));
};
render() {
return (
<React.Fragment>
<div onClick={this.showInd}>
<FontAwesomeIcon icon={faUser} className="fa-7x icon"/>
<span>individual</span>
</div>
<div onClick={this.showOrg}>
<FontAwesomeIcon icon={faUsers} className="fa-7x icon"/>
<span>organization</span>
</div>
<SignUpInd show={this.state.showInd} />
<SignUpOrg show={this.state.showOrg} />
</React.Fragment>
);
}
}
1.At the parent component use a function that changes the state.
state = {
showInd: false,
showOrg: false,
};
stateChange = () =>{
this.setState({showInd:!this.state.showInd})
}
2.Use an onClick function on the div it will give opposite value of what it is right now and pass it as a props to the next component
<div onClick={this.stateChange}> //this onClick just flips showInd to the opposite of what it is currently - that's working properly
<span>individual</span>
</div>
<SignUpInd show={this.state.showInd} stateChange= {this.stateChange} />
3.At the other end just recieve the props and console log it
const {show,stateChange} = this.props
console.log(show);
For My Class We Are Making A Website With React And Neither Me Or my Group Can Figure Out How To Just Render A Function In A Variable State And Make It Dynamic
My Code Is As Follows:
class App extends React.Component {
constructor(props)
{
super(props)
this.state = {
screen: this.home(),
movies: []
}
}
home = () =>{
this.state.movies.map((movie)=>{
return(
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
)
})
}
render(){
return(
<div>{this.state.screen}</div>
)
}
}
When I Run This The Error Reads
TypeError: Cannot read property 'movies' of undefined
You Can Assume That The Variable in State Movies Is Filled With An Array Of Movies Set By An API
Edit: The End Result I'm Attempting To Achieve Is To Return A Variable Or State Which Can Hold A Function Which Would Be The Different Screens/Pages To Be Rendered
If your movies array filled with data from any API call, then you can directly use that array to render the data,
class App extends React.Component {
constructor(props)
{
super(props)
this.state = {
movies: []
}
}
render(){
return(
<div>
{
this.state.movies.map((movie)=>{
return(
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
)
})
}
</div>
)
}
}
The root cause here is that this.state is not initialized when you're using it the home() invocation in the constructor.
Either way, you're not supposed to store rendered content within state.
Based on the comment, here's a refactoring, but I would recommend looking into an actual router like react-router instead.
const HomeView = ({ movies }) =>
movies.map(movie => (
<div>
<Popular
title={movie.title}
rating={movie.vote_average}
poster={movie.poster_path}
desc={movie.overview}
/>
</div>
));
const FavoritesView = ({ movies }) => <>something else...</>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: [],
view: "home",
};
}
render() {
let view = null;
switch (this.state.view) {
case "home":
view = <HomeView movies={this.state.movies} />;
break;
case "favorites":
view = <FavoritesView movies={this.state.movies} />;
break;
}
return (
<div>
<a href="#" onClick={() => this.setState({ view: "home" })}>
Home
</a>
<a href="#" onClick={() => this.setState({ view: "favorites" })}>
Favorites
</a>
{view}
</div>
);
}
}
I'm trying to do unit testing to a component using enzyme shallow rendering. Trying to test state activeTab of the component and it throws TypeError: Cannot read property state. my component Accordion. Accordion component jsx code
class Accordion extends Component {
constructor(props) {
super(props)
this.state = {
activeTab: 0
}
}
static defaultProps = {
tabs: [{title: 'Status'}, {title: 'Movement'}]
}
render() {
const { tabs } = this.props
, { activeTab } = this.state
return (
<div className={`accordion`}>
{tabs.map((t, i) => {
const activeClass = activeTab === i ? `accordion--tab__active` : ''
return(
<section key={i} className={`accordion--tab ${activeClass}`}>
<header className={`accordion--header`}>
<h4 className={`accordion--title`}>
<button onClick={() => {this._selectAccordion(i)}}>{t.title}</button>
</h4>
</header>
<div className="accordion--content">
{t.title}
Content
</div>
</section>
)
})}
</div>
)
}
_selectAccordion = activeTab => {this.setState({activeTab})}
}
export default Accordion
and Accordion.react.test.js
import { shallow } from 'enzyme'
import Accordion from './components/Accordion'
test('Accordion component', () => {
const component = shallow(<Accordion name={`Main`}/>)
expect(component.state('activeTab')).equals(0)
})
This could be a this scoping issue. With event handlers in React, you have to bind the event handler in the constructor to "this". Here is some info from React's docs about it:
You have to be careful about the meaning of this in JSX callbacks. In
JavaScript, class methods are not bound by default. If you forget to
bind this.handleClick and pass it to onClick, this will be undefined
when the function is actually called.
This is not React-specific behavior; it is a part of how functions
work in JavaScript. Generally, if you refer to a method without ()
after it, such as onClick={this.handleClick}, you should bind that
method.
class Accordion extends Component {
constructor(props) {
super(props)
this.state = {
activeTab: 0
}
// This binding is necessary to make `this` work in the callback
this._selectAccordion = this._selectAccordion.bind(this);
}
static defaultProps = {
tabs: [{title: 'Status'}, {title: 'Movement'}]
}
_selectAccordion(activeTab){
this.setState({activeTab : activeTab})
}
render() {
const { tabs } = this.props,
{ activeTab } = this.state
return (
<div className={`accordion`}>
{tabs.map((t, i) => {
const activeClass = activeTab === i ? `accordion--tab__active` : ''
return(
<section key={i} className={`accordion--tab ${activeClass}`}>
<header className={`accordion--header`}>
<h4 className={`accordion--title`}>
<button onClick={() => {this._selectAccordion(i)}}>{t.title}</button>
</h4>
</header>
<div className="accordion--content">
{t.title}
Content
</div>
</section>
)
})}
</div>
)
}
}
Your tests should verify how the component works but not "how to change a state". You need to throw new props into your component and get a result, and the result is expected.
I've tested my components with snapshots
This is an example of my current project
describe('<Component />', () => {
it('Page rendered', () => {
const rendered = renderComponent({
...testProps,
loadDataList,
loading: true,
});
expect(rendered).toMatchSnapshot();
});
});
I have just started using React and working on a small app, in the meantime I made a small show and hide modal. I wanted to know the way I have made it is a wrong way to do it. If this is an anti-pattern how should I go about it?
class App extends Component {
constructor(props) {
super(props);
this.state = {show: false};
this.showModal = this.showModal.bind(this);
}
render() {
return (
<div>
<h2 className={styles.main__title}>Helloooo!</h2>
<Modal ref='show'/>
<button onClick={this.showModal} className={styles.addtask}>➕</button>
</div>
);
}
showModal(){
this.setState({
show: true
});
this.refs.show.showModal();
}
}
The modal component which i have made is using this logic, it hooks the dom elements and modifies using the document.queryselector. Is this a right way to do the dom manipulation in react.
The modal code which i have used is this :
class Modal extends Component {
constructor() {
super();
this.hideModal = this.hideModal.bind(this);
this.showModal = this.showModal.bind(this);
this.state = { modalHook: '.'+styles.container };
}
render() {
return (
<div>
<div onClick={this.hideModal} className={styles.container}>
<div className={styles.container__content}>
<div className={styles.card}>
<div className={styles.card__header}>
<h2>Add new task</h2>
</div>
<div className={styles.card__main}>
<Input type="text" placeholder="enter the task title" />
<Input type="textarea" placeholder="enter the task details" />
</div>
<div className={styles.card__actions}>
</div>
</div>
</div>
</div>
</div>
);
}
showModal(){
let container = document.querySelector(this.state.modalHook);
container.classList.add(styles.show);
}
hideModal(e){
let container = document.querySelector(this.state.modalHook);
if(e.target.classList.contains(styles.container)){
container.classList.remove(styles.show);
}
}
}
Your example looks good and simple, but accordingly to this it is better don't overuse refs.
And also it might be helpful to lifting state up, like described here.
Here my example:
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {show: props.show};
}
componentDidUpdate(prevProps, prevState) {
let modal = document.getElementById('modal');
if (prevProps.show) {
modal.classList.remove('hidden');
} else {
modal.className += ' hidden';
}
}
render() {
return (
<div id="modal" className={this.state.show ? '' : 'hidden'}>
My modal content.
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {show: false};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
show: !prevState.show
}));
}
render() {
return (
<div>
<button onClick={this.handleClick}>
Launch modal
</button>
<Modal show={this.state.show} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Here i don't pretend for ultimate truth, but try to provide another option how you can reach desired result.
To do what you require you don't need to use refs at all. You can pass the state down the to child component as a prop. When the state updates the prop will automatically update. You can then use this prop to switch a class. You can see it in action on jsbin here
const Modal = (props) => {
return (
<div className={props.show ? 'show' : 'hide'}>modal</div>
)
}
const styles = {
main__title: 'main__title',
addtask: 'addtask'
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {show: false};
this.toggleModal = this.toggleModal.bind(this);
}
render() {
return (
<div>
<h2 className={styles.main__title}>Helloooo!</h2>
<Modal show={this.state.show} />
<button onClick={this.toggleModal} className={styles.addtask}>➕</button>
</div>
);
}
toggleModal(){
this.setState({
show: !this.state.show
});
}
}
ReactDOM.render(<App />, document.getElementById('root'));