Toggle modal in React - reactjs

I have a parent container which has a couple of child components. When the user clicks onClick={props.toggleReviewForm}, the function
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
toggles the reviewForm state to visible. It's visibility is set with reviewFormActive={reviewFormActive} in the child component and the parent has `this.state = {reviewFormActive: false} set in the constructor. I am passing
displayNameModalActive={displayNameModalActive}
into the child component for the modal, but getting the error
Uncaught TypeError: Cannot read property 'displayNameModalActive' of undefined at DisplayNameModal.render
Parent Container
class ReviewsContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
reviewFormActive: false,
displayNameModalActive: false
}
this.config = this.props.config
this.toggleReviewForm = this.toggleReviewForm.bind(this)
}
toggleReviewForm () {
this.setState(prevState => ({
reviewFormActive: !prevState.reviewFormActive,
displayNameModalActive: !prevState.displayNameModalActive
}))
}
render () {
const {
reviewFormActive,
displayNameModalActive
} = this.state
return (
<div className='reviews-container'>
<ReviewForm
config={this.config}
reviewFormActive={reviewFormActive}
toggleReviewForm={this.toggleReviewForm}
/>
{this.state.displayName &&
<div className='modal-container'>
<DisplayNameModal
bgImgUrl={this.props.imageUrl('displaynamebg.png', 'w_1800')}
config={this.config}
displayNameModalActive={displayNameModalActive}
displayName={this.state.displayName}
email={this.state.email} />
</div>
}
</div>
)
}
}
export default ReviewsContainer
Child Component (modal)
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName,
email: this.props.email.split('#')[0]
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div>
//Should only allow the modal to show if the username is the same as the email or there is no username available
{ props.displayNameModalActive && this.state.displayName === this.state.email || !this.state.displayName &&
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<div className='display-name-content'>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text"
defaultValue={this.state.displayName}
placeholder={this.state.displayName}
minLength="3"
maxLength="15"/>
<button
onClick={this.updateDisplayName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p className='cancel'>{contentStrings.displayNameModal.cancel}</p>
</div>
</div>
}
</div>
)
}
}
export default DisplayNameModal

Whyt this:
props.displayNameModalActive
and not this:
this.props.displayNameModalActive
?
Correct me if I'm wrong but render doesn't get props as argument.

Related

Child not rerendering when parent rerenders on props change

I have a page where I can manage different things like tokens for example. When I choose to create a token, a modal component shows up with list of currently added tokens and form where I have to fill the data in. The issue is - after I add the token, it doesn't immediately show up in the Modal, I have close and re-open modal to see the added token.
managePage.js:
constructor()
{
super()
this.state = { showToken: false, tokensList: []} // control if modal should render
}
showTokenModal = () =>
{
this.getTokens() // fetches and saves tokens list to this.state.tokensList
this.setState({showToken: !this.state.showToken});
}
render()
{
...
return(
...
{this.state.showToken && (<>
<Overlay/>
<Modal onClose={this.showTokenModal} show={this.state.showToken} title={"Create token"}>
{console.log("Refreshed modal")}
<div >
<label htmlFor="title" style={{fontWeight: "bold"}}>Tokens:</label>
{this.state.tokensList.length>0 && (this.state.tokensList.map((t, i) =>
{
return (<div key={i}>{t.tokens.map((token, index) =>
{return <div key={index} >{index+1}: {token} [{t._id}]
(<label style={{color: "red"}} onClick={()=>this.deleteToken(t, token)}><strong>X</strong></label>)</div>})}</div>)
})
)}
<input type="number" name="lvl" defaultValue={2} placeholder="Access Level" onChange={this.handleChange} />
<input name="token" placeholder="Token unique ID" onChange={this.handleChange} />
<div>
<button type="button" onClick={this.showTokenModal}>Cancel</button>
<button type="button" onClick={this.addToken}>Create</button>
</div>
</div>
</Modal>
</>)}
)
}
I only call props functions in Modal component, which won't re-render Modal itself, but will re-render managePage.js, and to my understanding - re-rendering managePage.js should also re-render Modal with the new tokensList, but it doesn't!!!
Modal.js:
import React from "react";
import styles from "../css/Modal.scss";
export default class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
mystyle: "two"
};
this.timer = this.timer.bind(this);
}
onClose = e => {
this.props.onClose && this.props.onClose(e);
};
timer ()
{
this.setState({mystyle: "modal__close"})
setTimeout(async () => {
this.onClose()
}, 150)
}
render() {
if (!this.props.show) {
return null;
}
let t = this.props.show ? "modal" : "modal__close";
return (
<div className={t} style={styles}>
<div className={this.state.mystyle}>
<h2>{this.props.title}</h2>
<div className="content">{this.props.children}</div>
</div>
</div>
);
}
}
So how can I make Modal refresh when tokens are created/changed?

React show/hide content

I'm trying to make a toggle content button with React. But I can only get it to open, not to close when I click on it again. Can someone please take a look and let me know what I need to change within the code to accomplish it?
Here's what I have so far:
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<div className="test-item">
<div className="test-item-container" onClick={() => {this.changeActiveLocation(index)}}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.activeLocation === index) ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
))}
)
}
}
Thank you!
You're setting the active location to be the same location that you've clicked already so the this.state.activeLocation === index is always true. I would refactor the locations to their own component with an isOpen state value that gets updated when the location is clicked. So like the following:
// test class
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
activeLocation: 0,
}
}
changeActiveLocation = (activeLocation) => {
this.setState({
activeLocation: activeLocation,
});
}
render() {
const activeLocation = company.locations[this.state.activeLocation];
return (
{company.locations.map((location, index) => (
<LocationItem location={location} onClick={() => this.changeActiveLocation(index)} />
))}
)
}
}
// LocationItem
class LocationItem extends React.Component {
state = { isOpen: false };
handleClick = () => {
this.setState(prevState => { isOpen: !prevState.isOpen});
// call parent click to set new active location if that's still needed
if(this.props.onClick) this.props.onClick;
}
render() {
return <div className="test-item">
<div className="test-item-container" onClick={this.handleClick}>
<div className="test-item-header">
<h3>Text goes here!</h3>
<a><FontAwesomeIcon icon={(this.state.isOpen ? 'times' : 'chevron-right'} /></a>
</div>
</div>
</div>
}
}

Passing API data from parent container to child component in React

I have a React container with a number of child components. One of which is supposed to be a modal that will show the user their name which is fetched from a user data api in the parent container. I should be able to pass the user data into the child with a prop, but must be missing something, as the display name does not show in the input as the value.
Parent Container
class ParentContainer extends React.Component {
constructor (props) {
super(props)
this.state = {
displayName: this.state.user.displayName
}
this.config = this.props.config
}
async componentDidMount () {
try {
const userData = await superagent.get(`/api/user`)
await this.setState({ user: userData.body })
console.log(userData.body.displayName) <===logs out user display name
} catch (err) {
console.log(`Cannot GET user.`, err)
}
}
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
</div>
)
}
}
export default ParentContainer
Child Component
class DisplayNameModal extends React.Component {
constructor (props){
super(props)
this.state = {
displayName: this.props.displayName
}
}
render (props) {
const {contentStrings} = this.props.config
return (
<div className='display-name-container' style={{ backgroundImage: `url(${this.props.bgImgUrl})` }}>
<h2 className='heading'>{contentStrings.displayNameModal.heading}</h2>
<p>{contentStrings.displayNameModal.subHeading}</p>
<input type="text" placeholder={this.props.displayName}/>
<button
onClick={this.submitName}
className='btn btn--primary btn--md'>
<span>{contentStrings.displayNameModal.button}</span>
</button>
<p>{contentStrings.displayNameModal.cancel}</p>
</div>
)
}
}
export default DisplayNameModal
I found that adding displayName: userData.body.displayName to setState and then wrapping the component in the parent with
{this.state.displayName &&
<div>
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
</div>
}
works as the solution.
The prop should be passed by:
<DisplayNameModal
config={this.config}
displayName={this.state.displayName} />
where you are using:
<DisplayNameModal
config={this.config}
displayName={this.displayName} />
You have set the displayName on state in the parent, anything you refer to from state should be referred to as this.state.foo, where as any method on that component can be referred to as this.foo.
First of all, you fetch the data in wrong way, you can check it here:
componentDidMount () {
superagent.get(`/api/user`).then(res => this.setState({ user: res.body }))
}
the second one, initialize default state for displayName, for example, an empty string, it will be replaced when promise retrieves data data from the server:
constructor (props){
super(props)
this.state = {
displayName: ''
}
}
and pass this state as props to your child component:
render () {
return (
<div className='reviews-container'>
<ReviewForm
config={this.config} />
<ReviewList
reviews={reviews}
ratingIcon={this.ratingIcon}
/>
<DisplayNameModal
config={this.config}
displayName={this.props.displayName} />
</div>
)
}
in your child component, you can simply call this props:
<input type="text" placeholder={this.props.displayName}/>

React - pass dynamic value to child

I have an app that uploads files via a standard <input type="file"/>. I'm trying to pass the file size of the chosen file(s) to the child to see if it's above a certain size, and if so, display an error. I know you have to pass state values down as props, but I'm unsure as to where/how to call the function to get an updated value. Any help is appreciated.
Edit: I am using the react jsonschema form to build the form: https://github.com/mozilla-services/react-jsonschema-form. Declaring the schemas before the Parent class.
Parent
const schema = {
type: 'object',
required: ['file'],
properties: {
file: { type: 'string', format: 'data-url', title: 'File' }
}
}
const FileWidget = (props) => {
return (
<input type="file" id="fileName" required={props.required} onChange={(event) => props.onChange(event.target.value)} />
)
}
const uiSchema = {
file: {
'ui:widget': FileWidget,
classNames: "uiSchema"
}
}
class Parent extends Component {
constructor(props) {
super(props);
this.state = { fileSize: 0 };
this.getFileSize = this.getFileSize.bind(this);
getFileSize(){
this.setState({fileSize: document.getElementById("fileName").files[0].size});
console.log("FILESIZE:: ", this.state.fileSize);
} //where to call to update the file size?
render() {
return (
<div className="container">
<FileUpload schema={schema} uiSchema={uiSchema} fileSize={this.state.fileSize} />
</div>
)
}
}
export default Parent;
Child
class Child extends Component {
constructor(props) {
super(props);
this.state = { formData: {} };
this.handleSubmit = this.handleSubmit.bind(this);
}
render() {
return (
<div className="container">
<Form
schema={this.props.schema}
uiSchema={this.props.uiSchema}
formData={this.state.formData}
onChange={({ formData }) => this.setState({ formData })}
onSubmit={this.handleSubmit}
>
<div>
<button type="submit" className="btn btn-info">Convert</button>
</div>
</Form>
<div hidden={this.props.fileSize > 100 ? false : true }><h4>File size exceeded.</h4></div>
</div>
)
}
}
export default Child;
class Parent extends Component {
constructor(props) {
super(props);
this.state = { fileSize: 0 };
this.getFileSize = this.getFileSize.bind(this);
getFileSize(){
this.setState({fileSize: document.getElementById("fileName").files[0].size});
console.log("FILESIZE:: ", this.state.fileSize);
} //where to call to update the file size?
componentDidMount(){
// you can call the getFilesize here
this.getFileSize();
}
render() {
return (
<div className="container">
<FileUpload fileSize={this.state.fileSize} />
</div>
)
}
}
export default Parent;
child Component
class FileUpload extends Component {
constructor(props) {
super(props);
this.state = { formData: {} };
this.handleSubmit = this.handleSubmit.bind(this);
}
render() {
return (
<div className="container">
<Form
formData={this.state.formData}
onChange={({ formData }) => this.setState({ formData })}
onSubmit={this.handleSubmit}
>
<div>
<button type="submit" className="btn btn-info">Convert</button>
</div>
</Form>
<div style={{display:this.props.fileSize > 100 ? "block": "none" }><h4>File size exceeded.</h4></div>
</div>
)
}
}
export default FileUpload;
I think you want to show the error message when file size exceeds given size
you can also use componentWillMount to call getfilesize
it actually depends document.getElementById("fileName")
it that particular element has populated the data by the time your parent componentWill Mount you can use componentWillMount lifecycle hook
Parent Component
class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
onFileSelected(e) {
var fileSize = e.target.files.length > 0 ? e.target.files[0].size : false;
if (fileSize)
this.setState({ fileSize });
}
render() {
return (
<div className="App">
<input type="file" onChange={this.onFileSelected.bind(this)} />
<FileUpload fileSize={this.state.fileSize}></FileUpload>
</div>
);
}
}
child Component
class FileUpload extends Component {
render() {
return (
<div>
{this.props.fileSize > 100 ? <h2 >File size exceeds 100</h2> : null}
</div>
);
}
}
in the above code what i did is created <input type="file"/> in parent component and attached a onchange event to it.
Got it to work. Passed the function to the child:
<FileUpload fileSize={this.getFileSize.bind(this)} />
Then in the child added a setState to the form's onChange to call the function:
onChange={({ formData }) => { this.setState({ formData }); this.setState({fileSize:this.props.fileSize()})}}
and displayed the error message accordingly:
<div style={{display: this.state.fileSize > 100 ? "block": "none" }><h4>File size exceeded.</h4></div>

React Passing state to sibling component and up to parent class

Very very new to React and I seem to be stuck. This is a simple Todo app, I basically have 3 components, the base component, an input component and a task component. I have figured out how to edit the state within each component but I am having trouble passing state from component to component.
class App extends Component {
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput />
<Task taskState={true} text="task one" />
<Task taskState={true} text="task two" />
<Task taskState={true} text="task three" />
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
update(e) {
this.setState({inputValue: e.target.value});
console.log(this.state);
}
taskCreate(e) {
this.setState({text: this.state.inputValue, completeState: false});
console.log('button clicked');
console.log(this.state);
}
render () {
return (
<div className="taskInputContainer">
<TaskInputField update={this.update.bind(this)} taskCreate={this.taskCreate.bind(this)} />
</div>
)
}
}
class Task extends Component {
constructor(props) {
super();
this.state = {
completeState: false
}
}
toggleTask (e) {
this.setState({
completeState: !this.state.completeState
});
}
delete (item) {
}
render() {
return (
<div className="taskContainer" onClick={this.toggleTask.bind(this)}>
<div className={"taskState " + this.state.completeState}></div>
<div className={"taskText " + this.state.completeState }>{this.props.text}</div>
<div className="taskDelete"><i className="fa fa-times-circle-o" aria-hidden="true"></i></div>
</div>
);
}
}
const TaskInputField = (props) =>
<div className="taskInputContainer">
<input type="text" className="taskInputField" onChange={props.update}/>
<i className="fa fa-plus-circle" aria-hidden="true" onClick={props.taskCreate}></i>
</div>;
Task.propTypes = {
text: PropTypes.string.isRequired,
completeState: PropTypes.bool
};
Task.defaultProps = {
text: 'Task',
completeState: false
};
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
export default App;
So in the TaskInput has its own state that I can update but how do I pass that up to the parent component to update and add a Task component? Also how do I add a Task component without re-rendering the whole thing?
This issue is documented in detail in the article 'lifting the state up' in React's documentation.
TLDR, you create a handler that updates the state of the current component and pass it to children as props. In the example below (a modified version of your code), I passed down the methods that changes the state of component App, into its children components (TaskInput and Tasks).
class App extends React.Component {
constructor() {
super();
this.state = {
tasks: [],
}
}
addTask = (e, text) => {
e.preventDefault();
const newTask = {
id: new Date().getTime(),
done: false,
text
};
const newTasks = this.state.tasks.concat([newTask]);
this.setState({
tasks: newTasks
})
}
toggleTask = (id) => {
const updatedTask = this.state.tasks.filter(task => task.id === id);
updatedTask[0].done = !updatedTask[0].done;
const newTasks = this.state.tasks.map(task => {
if (task.id === id) {
return updatedTask[0];
}
return task;
});
this.setState({
tasks: newTasks
});
}
render() {
return (
<div id="appContainer">
<HeaderTitle />
<TaskInput addTask={this.addTask} />
{
this.state.tasks.length > 0 ? <Tasks tasks={this.state.tasks} toggleTask={this.toggleTask}/> : <div>no tasks yet</div>
}
</div>
);
}
}
class TaskInput extends React.Component {
constructor(props) {
super(props);
this.state = {
currentInput: ''
}
}
handleChangeText = (e) => {
this.setState({
currentInput: e.target.value,
})
}
render() {
return (<form>
<input type="text" value={this.state.currenInput} onChange={this.handleChangeText}/><input type="submit" onClick={(e) => this.props.addTask(e, this.state.currentInput)} value="Add Task"/></form>)
}
}
const Tasks = (props) => (
<div>
{
props.tasks.map(task => (
<div
style={ task.done ? { textDecoration: 'line-through'} : {} }
onClick={() => props.toggleTask(task.id)}
>{task.text}</div>
))
}
</div>
);
const HeaderTitle = () => (
<h1>Davids Todo List</h1>
);
ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Resources