React from mapped components open dialog - javascript

So my application is growing, and I am starting to wonder, what is the best approach in my case, to open dialog from mapped component.
So currently, I have events array of object, which i map and create new components.
EventFeed.js
events.map(event => <EventCard key={event._id} event={event}/>)
What i want to do from each eventcard on "More info" button click call dialog and pass event id to that dialog, my current aproach looks like this:
SingleEventCard
class RecipeReviewCard extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false,
isgoing: this.props.isgoing,
showModal1: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) { // switch the value of the showModal state
this.setState({
showModal1: !this.state.showModal1
});
}
getComponent = () => {
if (this.state.showModal1) { // show the modal if state showModal is true
return <EventView eventid={this.props.event._id} />;
} else {
return null;
}
}
render() {
return {
<Button onClick={this.handleClick}>More info</Button>
{this.getComponent()}
}
}
In render i have another views which I wont write(since no reason bunch of inputs and displays), the problem is that when I open dialog it messes up my event card style. Also I need to click two times next time in order to open. Can someone can offer more generic approach, where I can render that dialog in other component or maybe something different?

Related

Calling a function in child from parent says "not a function"?

I might be misunderstanding how parent-child relations are supposed to work in React (new to it) but the following should work in my mind:
I have a parent called <Home/> and in it, I have a child called <ProjectDialog> which is a Google Material Dialog that I'm going to customize after I get this to work.
In the child I have the following code:
handleOpen = () => {
this.setState({ open: true });
};
Pretty normal stuff honestly. But I wanna be able to change the open state from the parent, which I attempt here:
let dialog = <ProjectDialog/>;
class Home extends Component {
handleCardClick = id => {
dialog.handleOpen();
};
But when I click any of the elements that are supposed to trigger this dialogue I get the error that handleOpen() is not a function.
Is there some other way I could do this? Would it make sense to store the open state in props instead and trigger it that way?
That is not the way things are supposed to work.
You have to do it declaratively, meaning that the open/close information should be kept in the parent and transmitted with props to the child.
Something like this:
class Home extends Component {
state = {
isDialogOpen: false
}
handleOpen = () => this.setState({ isDialogOpen: true })
render() {
return (
...
<ProjectDialog isOpen={ this.state.isDialogOpen } />
...
<button onClick={ this.handleDialogOpen }>
Open project dialog
</button>
...
)
}
}

Change the state when clicking outside a component in React

I have a dropdown as is shown in the following image:
When I click the folder icon it opens and closes because showingProjectSelector property in the state that is set to false.
constructor (props) {
super(props)
const { organization, owner, ownerAvatar } = props
this.state = {
owner,
ownerAvatar,
showingProjectSelector: false
}
}
When I click the icon, it opens and closes properly.
<i
onClick={() => this.setState({ showingProjectSelector: !this.state.showingProjectSelector })}
className='fa fa-folder-open'>
</i>
But what I'm trying to do is to close the dropdown when I click outside it. How can I do this without using any library?
This is the entire component: https://jsbin.com/cunakejufa/edit?js,output
You could try leveraging onBlur:
<i onClick={...} onBlur={() => this.setState({showingProjectSelector: false})}/>
I faced same issue with you. Solved after reading this:
Detect click outside React component
Please try:
You should use a High Order Component to wrap the component that you would like to listen for clicks outside it.
This component example has only one prop: "onClickedOutside" that receives a function.
ClickedOutside.js
import React, { Component } from "react";
export default class ClickedOutside extends Component {
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside);
}
handleClickOutside = event => {
// IF exists the Ref of the wrapped component AND his dom children doesnt have the clicked component
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
// A props callback for the ClikedClickedOutside
this.props.onClickedOutside();
}
};
render() {
// In this piece of code I'm trying to get to the first not functional component
// Because it wouldn't work if use a functional component (like <Fade/> from react-reveal)
let firstNotFunctionalComponent = this.props.children;
while (typeof firstNotFunctionalComponent.type === "function") {
firstNotFunctionalComponent = firstNotFunctionalComponent.props.children;
}
// Here I'm cloning the element because I have to pass a new prop, the "reference"
const children = React.cloneElement(firstNotFunctionalComponent, {
ref: node => {
this.wrapperRef = node;
},
// Keeping all the old props with the new element
...firstNotFunctionalComponent.props
});
return <React.Fragment>{children}</React.Fragment>;
}
}
If you want to use a tiny component (466 Byte gzipped) that already exists for this functionality then you can check out this library react-outclick.
The good thing about the library is that it also lets you detect clicks outside of a component and inside of another. It also supports detecting other types of events.
Using the library you can have something like this inside your component.
import OnOutsiceClick from 'react-outclick';
class MyComp extends Component {
render() {
return (
<OnOutsiceClick
onOutsideClick={() => this.setState({showingProjectSelector: false})}>
<Dropdown />
</OnOutsiceClick>
);
}
}
Wrapper component - i.e. the one that wrapps all other components
create onClick event that runs a function handleClick.
handleClick function checks ID of the clicked event.
When ID matches it does something, otherwise it does something else.
const handleClick = (e) => {
if(e.target.id === 'selectTypeDropDown'){
setShowDropDown(true)
} else {
setShowDropDown(false);
}
}
So I have a dropdown menu that appears ONLY when you click on the dropdown menu, otherwise it hides it.

Why is the new list only showing after an input change in react?

After submitting a new message through 'send' to add to the list I want rendered, no list is updated on the client screen. However, once I do anything in the input box AFTER that point, the item is then shown. So it's working...just not updating until after the modifier change call and not working how I want it to. :D
I think this is because hitting anything in the input afterward is updating the page, so the componentDidUpdate is called, but I'm unsure of why renderChatList isn't updating when I prefer it to.
What can I do or move around to fix that?
export class Chat extends React.Component{
constructor(props){
super(props);
this.state = {
text: '',
nextId: 0,
chatList: []
};
}
componentDidMount(){
socket.on('newMessage', (message) => {this.state.chatList.push({...message}) });
}
onTextChange(e){
this.setState({text: e.target.value});
}
renderChatList() {
return this.state.chatList.map((chatItem) => {
return <li key={chatItem.createdAt}>{chatItem.from}: {chatItem.text}</li>
});
}
send(e) {
e.preventDefault();
socket.emit('createMessage', {
from: this.props.owner,
text: this.state.text
}, function(){});
}
render(){
return (
<div>
<form onSubmit={this.send.bind(this)}>
<input type="text" onChange={this.onTextChange.bind(this)} placeholder="message"/>
<button>Send chat</button>
</form>
<ul>{this.renderChatList()}</ul>
</div>
)
}
};
Only by changing the state with setState() can the component rerender. You need to change the way you add a comment to the state to something like:
socket.on('newMessage', (message) => {
this.setState({ chatList: [...this.state.chatList, {...message}] })
});
The reason why the messages currently show after you type in some input is because changing the input changes the state with setState() and causes the component to rerender.

React: How do I make Child components in an Array re-render upon prop change?

I have a MainComponent which has several buttons with different texts. Upon button click the first time for any of them, it'll create a new window component. There is a flag that prevents it from creating more Windows unless this flag changes for some reason (I didn't need to include it here). Assume the code can create multiple Window components and add it to the list and render it successfully, which it does but if the mode is changed, click the buttons again won't create more components.
I render this array of objects by creating an state array and state flag and add new Window component to that array everytime a new one is created and render that array.
My problem is that when new Window create flag is off (flagObj !== null), I click any of the buttons which pass in differing values to the current windows, and I want the values to update. But the values DO NOT update. Each window.body prop accesses this.state.currentWindowData which is what I'm changing everytime a button gets clicked in this mode, and using dev tool I see that the state gets changed accordingly.
For some reason, it is not propagating down to the children Window components in the list. What is the problem?
MainComponent
constructor(props) {
super(props)
this.state = {renderWindow: false, windowArray: []}
this.manage= this.manage.bind(this)
}
openWindow() {
const newWindow = <Window key={shortid.generate()} body={this.state.currentWindowData}/>;
this.setState({
renderWindow: true,
windowArray: [newWindow, ...this.state.windowArray]
})
}
modify(val) {
this.setState({currentWindowData: val});
}
manageWindow(val) {
if (flagObj === null) {
this.setState({currentWindowData: val}, () => {
this.openWindow();
});
} else {
this.modifyWindow();
}
}
render() {
return (
<div>
<button onClick = {() => this.manage("text1")}/>
<button onClick = {() => this.manage("text2")}/>
<button onClick = {() => this.manage("text3")}/>
{this.state.renderWindow ? this.state.windowArray : null}
</div>
)
}
This is the window component. I render this.props.body (the state data passed in) and also do this.props.bodyData (set from componentWillReceiveProps), but neither update.
Window
constructor(props) {
super(props);
}
componentWillReceiveProps(nextProps) {
this.setState({bodyData: nextProps.body});
}
render() {
return (
<div>
{this.props.body}
{this.state.bodyData}
</div>
);
}

Click event improperly changing all React components of the same type

I have an onClick event set up for a component that is rendered multiple times. When this onClick is triggered, text inside a button determined by a JS variable is changed. However, when I click a button for one component the text changes for all other components of this same type. Here is the code:
FormEntity.js:
import React from 'react';
let hasFile = false;
let uploadBtnText = 'Simulate file upload';
const FormEntity = (props) => {
const handleClick = () => {
if(hasFile) {
props.formInstanceRemoved(props.instanceId);
hasFile = false;
uploadBtnText = 'Simulate file upload';
} else {
props.formInstanceUploaded(props.instanceId, props.blueprintId);
hasFile = true;
uploadBtnText = 'Remove file';
}
};
return (
<div>
<button type="button" onClick={handleClick}> {uploadBtnText} </button>
</div>
);
};
export default FormEntity;
Here is a picture that should help further show the problem.
Clicking a button should only effect the text and whatever else is within that button, not all the other components too. Any advice?
You probably include your FormEntity per import / require. Therefore all instances of the imported component FormEntity share the same uploadBtnText variable.
Whenever it is changed in an instance of a particular FormEntity, because it is shared, all other instances of FormEntity get also updated.
I would suggest to refactor your stateless functional FormEntity to a standard component and write uploadBtnText in its state.
You want each button to have its own state, so you should define the buttons as stateful ones and use setState. Your current implementation changes module-level variables, which are shared by all instances of the button.
const FormEntity extends React.Component {
constructor(props) {
super(props);
this.state = {
hasFile: false,
uploadBtnText: 'Simulate file upload',
};
}
handleClick = () => {
if (this.state.hasFile) {
this.props.formInstanceRemoved(props.instanceId);
this.setState({
hasFile: false,
uploadBtnText: 'Simulate file upload',
});
} else {
this.props.formInstanceUploaded(props.instanceId, props.blueprintId);
this.setState({
hasFile: true,
uploadBtnText: 'Remove file',
});
}
};
render() {
return (
<div>
<button type="button" onClick={this.handleClick}> {uploadBtnText} </button>
</div>
);
}
};

Categories

Resources