I have a registration view where in my table i have command to show modal with confirmation:
(...)
render: (rowData) => (
<button
onClick={() => RenderModals(rowData, 'DELETE_USER_MODAL')}
>
Remove
</button>
),
(...)
My RenderModals function looks like this:
type RenderModalProps = {
data: any;
modalCommand: string;
};
export const RenderModals = (data, modalCommand) => {
console.log(data);
switch (modalCommand) {
case 'DELETE_USER_MODAL':
return <DeleteUserModal data={data} />;
case 'SOME_MODAL':
return console.log('some modal');
default:
undefined;
}
};
and I can see console.log(data) in the example above. But... I cant see any console.log from DeleteUserModal component.
DeleteUserModal:
type DeleteUserModalProps = {
data: any;
};
export const DeleteUserModal = ({ data }: DeleteUserModalProps) => {
console.log(`show data ${data}`);
return <div>some text...</div>;
};
I can't figure out what I'm doing wrong ?
Why console.log from DeleteUserModal doesn't trigger?
The way you currently have things set up, this would work:
class RegistrationExampleOne extends React.Component {
constructor(props) {
super(props);
this.state = {component: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({component: RenderModals(rowData, 'DELETE_USER_MODAL')})}>Remove</button>
{this.state.component}
</div>
);
}
}
Option one is not necessarily the better way of doing things, but it is more dynamic.
Option two (as mentioned in the comments by #Brian Thompson) would be similar to this:
import DeleteModal from './path';
class RegistrationExampleTwo extends React.Component {
constructor(props) {
super(props);
this.state = {showDeleteModal: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({showDeleteModal: true})}>Remove</button>
{this.state.showDeleteModal && <DeleteModal data={rowData} />}
</div>
);
}
}
Related
I am trying to toggle a modal from separate components. the first most common component is my app.tsx so i set the state in that file.
type TokenUpdateType = {
sessionToken: string | undefined | null,
createActive: boolean
}
export default class App extends Component<{}, TokenUpdateType> {
constructor(props: TokenUpdateType) {
super(props)
this.state = {
sessionToken: undefined,
createActive: false
}
...
toggleModal = () => {
this.setState({createActive: !this.state.createActive})
}
return
<Home isOpen={this.state.createActive} toggleModal={this.toggleModal} />
my home component takes these props and passes again to another component
type AuthProps = {
isOpen: boolean
toggleModal: () => void
...
}
const Home = (props: AuthProps) => {
return(
<>
<Sidebar sessionToken={props.sessionToken} toggleModal={props.toggleModal}
<ChannelEntryModalDisplay sessionToken={props.sessionToken} isOpen={props.isOpen} toggleModal={props.toggleModal}/>
</>
)
}
isOpen gets passes to my modal component and is used in this component
type AuthProps = {
isOpen: boolean
toggleModal: () => void
...
}
const ChannelEntryModalDisplay = (props: AuthProps) => {
return(
<div>
<Modal show={props.isOpen}>
<ChannelEntry sessionToken={props.sessionToken}/>
<Button className='button' type='button' outline onClick={props.toggleModal}>close</Button>
</Modal>
</div>
)
}
my modal is not showing even when i set createactive to true. i believe i may be passing props incorrectly but im not sure what i am doing incorrectly. i appreciate any feedback.
try to create a new state from the props:
const [createActive, setCreateActive] = useState<boolean>()
constructor(props: TokenUpdateType)
{
super(props)
setCreateActive(props.createActive)
}
useEffect(() => {
setCreateActive(props.createActive) // update the state when props changes
}, [props])
...
toggleModal = () => {
this.setCreateActive(!createActive)
}
<Home isOpen={createActive} toggleModal={this.toggleModal} />
I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.
I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.
Thank you!
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected }, () => {
this.sectionRef.current.changeSection(this.state.section)
})
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<Layout>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</Layout>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { this.props.selectSection("projects")}}>
{" "}
Projects
</span>
</header>
)
There seemed to be a couple of issues with your example code:
Missing closing div in Header
Using this.props instead of props in onclick in span in Header
The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected })
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<div>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</div>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { props.selectSection("projects")}}>
{" "}
Projects
</span>
</div>
</header>
)
ReactDOM.render(
<IndexPage />,
document.getElementsByTagName('body')[0]
);
.visible {
opacity: 1
}
.invisible {
opacity: 0
}
<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>
There is a markup error in your code in Header component - div tag is not closed.
Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.
As #Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})
Any way here is a working example of what you trying to achieve:
// #flow
import * as React from "react";
import Header from "./Header";
type
Properties = {};
type
State = {
section: string,
headerVisible: boolean,
};
class IndexPage extends React.Component<Properties, State> {
static defaultProps = {};
state = {};
constructor(props) {
super(props);
this.state = {
section: "",
headerVisible: true,
};
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
setTimeout(
() => this.setState(
prevState => ({
section: sectionSelected,
headerVisible: !prevState.headerVisible
}),
() => console.log("Debug log: \n", this.state)
),
325
);
}
render(): React.Node {
const {section, headerVisible} = this.state;
return (
<React.Fragment>
<Header selectSection={this.displaySection} headerVisible={headerVisible} />
<br/>
<div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
</React.Fragment>
)
}
}
export default IndexPage;
and Header component
// #flow
import * as React from "react";
type Properties = {
headerVisible: boolean,
selectSection: (section: string) => void
};
const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
const headerRef = React.useRef(null);
return (
<React.Fragment>
<header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => selectSection("projects")}>Projects</span>
</div>
</header>
<br/>
<div>Header class name: {headerRef.current && headerRef.current.className}</div>
</React.Fragment>
);
};
export default ComponentName;
I have 3 components. App.js - Main. localLog.jsx stateless, LoadBoard.jsx statefull. I want to Take string of data from LoadBoard and display it in localLog.jsx. The problem is that I can't figure it out why LocalLog is not displaying on screen.
console.log(this.data.Array) in App.jsx localLog is ["configuration"]
(2) ["configuration", "It's good configuration"]
App.jsx
class App extends Component {
constructor(props) {
super(props);
this.dataArray = [];
this.state = {
headers: []
};
this.localLog = this.localLog.bind(this);
}
localLog(data) {
if (data) {
this.dataArray.push(data);
console.log(this.dataArray);
this.dataArray.map(data => {
return <LocalLog info={data} />;
});
}
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.localLog} />
<pre id="log_box">{this.localLog()}</pre>
</>
);
}
}
localLog.jsx
let localLog = props => {
return (
<pre className={classes.background}>
<ul className={classes.ul}>
<li>{props.info}</li>
<li>hello world</li>
</ul>
</pre>
);
};
export default localLog;
LoadBoard.jsx
class LoadBoard extends Component {
constructor(props) {
super(props);
this.state = {
positionToId: []
};
}
componentDidMount() {
this.props.localLog("configuration");
this.props.localLog(`It's good configuration`);
}
render() {
return (
<div>
<h1>Nothing interesting</h1>
</div>
);
}
}
You are not returning anything from the localLog method, should be:
return this.dataArray.map(data => {
return <LocalLog info={data} />;
});
EDIT:
here is what your App component should look like.
class App extends Component {
constructor(props) {
super(props);
this.state = {
headers: [],
logs: []
};
this.addLog = this.addLog.bind(this);
}
// Add log to state
addLog(log) {
this.setState(state => ({
...state,
logs: [...state.logs, log]
}));
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.addLog} />
<pre id="log_box">
{this.state.logs.map(log => {
return <LocalLog info={log} />;
})}
</pre>
</>
);
}
}
you should use setState method in order to re-render the component.
you can try this.
class App extends Component {
constructor(props) {
super(props);
this.state = {
headers: [],
dataArray: []
};
this.localLog = this.localLog.bind(this);
}
localLog(data) {
if (data) {
this.state.dataArray.push(data);
this.setState({dataArray: this.state.dataArray})
}
}
render() {
return (
<>
<LoadBoard apiBase={this.state.apiBase} localLog={this.localLog} />
<pre id="log_box">{this.state.dataArray.map(i => <LoaclLog info={i}/>)}</pre>
</>
);
}
}
I want to create a react component instance and render it in a static place programmatically.
My use-case is that I open a sequence of dialogs in an unknown length and when I get a response from a dialog I open the next.
I want to do something like:
const DialogExample = () => ({ question, onAnswer }) =>
(<div>
{question}
<button onClick={onAnswer}>answer</button>
</div>);
class SomeComponent extends React.Component {
async start() {
const questions = await getSomeDynamicQuestions();
this.ask(questions);
}
ask(questions) {
if (questions.length === 0) {
// DONE.. (do something here)
return;
}
const current = questions.pop();
React.magicMethod(
// The component I want to append:
<DialogExample
question={current}
onAnswer={() => this.ask(questions)}
/>,
// Where I want to append it:
document.getElementsByTagName('body')[0]);
}
render() {
return (
<div>
<button onClick={this.start}>start</button>
</div>);
}
}
I know that's not very "react-like", and I guess the "right" way of doing it will be storing those questions in state and iterate over them in "someComponent" (or other) render function, but still, I think that this pattern can make sense in my specific need.
Sounds like a case for Portals. I'd recommend doing something like this:
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.body = document.getElementsByTagName('body')[0];
this.state = {
questions: [],
}
}
async start() {
const questions = await getSomeDynamicQuestions();
this.setState({ questions });
}
nextQuestion() {
this.setState(oldState => {
const [first, ...rest] = oldState.questions;
return { questions: rest };
})
}
render() {
const { questions } = this.state;
return (
<div>
<button onClick={this.start}>start</button>
{questions.length > 0 && ReactDOM.createPortal(
<DialogExample
question={questions[0]}
onAnswer={() => this.nextQuestion()}
/>,
this.body,
)}
</div>
);
}
}
I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}