I need ideas about how to solve the following problem in Reactjs:
TextEditor -> Toolbar -> PickColorBox
I have component's relationship (parent -> children) as those listed above.
The problem: The component Toolbar has the state showPickColorBox: false that represents whether the component PickColorBox is visible or not. This state is fired by a button's click event. What I need is the button be capable of toggle the boolean value to the correct state, that is, true if the component is visible, false if it's not. But every time the button event is fired the Toolbar's constructor is called and the state showPickColorBox is set to false, even if the PickColorBox component is visible.
My solution: What I need is to track whether the component PickColorBox is visible or not to set the state to the proper value. Reading some codes I saw examples where a class is instantiated in the root component to keep tracking the events. I try to reproduce the example but it didn't work, I wasn't able to passing as props the function inside the class. I'm even not sure whether it's possible to instantiated a class in javascript, so I need some guide here.
Any help on how to solve, any other solution to this is really appreciated!
I change the code bellow to simplify, so typos in the code isn't the problem, unless is about the instantiated class and how to do it. Another thing, just to clarify the code, I'm using Draftjs framework where the component Editor, the states EditorState and onChange are inheritance.
Root component
export default class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.onChange = (editorState) => this.setState({editorState});
this.modalHandler = new ModalHandler();
}
render() {
const {editorState} = this.state;
return (
<div className={'editor-root'} >
<div className={'editor-toolbar'}>
<Toolbar
editorState={editorState}
onChange={this.onChange}
modalHandler={this.modalHandler}
/>
</div>
<div className={'editor-textarea'} >
<Editor
editorState={editorState}
onChange={this.onChange}
/>
</div>
</div>
);
}
}
ModalHandler class
export default class ModalHandler {
let boolShowComponent = false;
toogleShowComponent = (): boolean => {
return !boolShowComponent;
};
}
Toolbar Component
export default class Toolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
showPickColorBox: false
};
}
_onPickColorClick() {
let bool = this.props.modalHandler.toogleShowComponent()
this.setState({
showPickColorBox: bool,
});
}
render() {
return (
<div className={'ToolbarEditor'} >
{this._onPickColorClick.bind(this)}>PickColor</button>
{
this.state.showPickColorBox ?
<PickColorBox
editorState={this.props.editorState}
onChange={this.props.onChange}
/> :
null
}
</div>
);
}
}
Instead of handling Toolbar using showPickColorBox, try to define the same in TextEditor and pass it as props. Now, to update showPickColorBox, define a method in TextEditor and pass it as props.
export default class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
//define here
showPickColorBox: false
};
this.onChange = (editorState) => this.setState({editorState});
this.modalHandler = new ModalHandler();
}
//definehere
_onPickColorClick(bool) {
this.setState({
showPickColorBox: bool,
});
}
render() {
const {editorState} = this.state;
return (
<div className={'editor-root'} >
<div className={'editor-toolbar'}>
<Toolbar
editorState={editorState}
onChange={this.onChange}
modalHandler={this.modalHandler}
//pass here
_onPickColorClick={this._onPickColorClick}
/>
</div>
<div className={'editor-textarea'} >
<Editor
editorState={editorState}
onChange={this.onChange}
/>
</div>
</div>
);
}
}
Now call from toolbar:
export default class Toolbar extends React.Component {
constructor(props) {
super(props);
}
_onPickColorClick() {
let bool = this.props.modalHandler.toogleShowComponent();
//use here
this.props._onPickColorClick(bool);
}
render() {
return (
<div className={'ToolbarEditor'} >
{this._onPickColorClick.bind(this)}>PickColor</button>
{
this.state.showPickColorBox ?
<PickColorBox
editorState={this.props.editorState}
onChange={this.props.onChange}
/> :
null
}
</div>
);
}
}
I find the solution to what I was looking for. It happens that I forgot to createing a properly EventHandler class to track the events fired in children component. So here is the solution:
Root component
export default class TextEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.onChange = (editorState) => this.setState({editorState});
/* this name makes more sense instead of modalHandler */
this.toolbarEventHandler = new ToolbarEventHandler();
}
render() {
const {editorState} = this.state;
return (
<div className={'editor-root'} >
<div className={'editor-toolbar'}>
<Toolbar
editorState={editorState}
onChange={this.onChange}
toolbarEventHandler={this.toolbarEventHandler}
/>
</div>
<div className={'editor-textarea'} >
<Editor
editorState={editorState}
onChange={this.onChange}
/>
</div>
</div>
);
}
}
ToolbarEventHandler (ModalHandler)
/*I will change this class to hold more than one event */
/* Probably change to an array */
export default class ToolbarEventHandler {
constructor() {
this.boolShowComponent = false;
}
get boolShowComponent() {
return this.boolShowComponent;
}
set boolShowComponent(bool){
this.boolShowComponent = bool;
}
}
Toolbar
export default class Toolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
showPickColorBox: false
};
}
_onPickColorClick() {
this.props.toolbarEventHandler.boolShowComponent = !this.props.toolbarEventHandler.boolShowComponent;
this.setState({
showComponent: this.props.toolbarEventHandler.boolShowComponent,
});
}
render() {
return (
<div className={'ToolbarEditor'} >
{this._onPickColorClick.bind(this)}>PickColor</button>
{
this.state.showPickColorBox ?
<PickColorBox
editorState={this.props.editorState}
onChange={this.props.onChange}
/> :
null
}
</div>
);
}
}
Related
When chartValue: true an object appears on the chart.
class Chart extends Component {
constructor(props) {
super(props);
this.state = {
chartValue: false,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
chartValue: nextProps.chartValue
});
}
render() {
return (
<div>{this.state.chartValue}</div>
);
}
}
I want to change the value to true via a button in my app component.
class App extends Component {
constructor(props) {
super(props);
this.state = ({
chartValue: false,
});
}
callChartValue() {
this.setState({
chartValue: true
})
}
render() {
return (
<div>
<button onClick={this.callChartValue}>call</button>
<Chart chartValue={this.state.chartValue} />
</div>
)}
}
}
It seems like changing the state in my App.js doesnt translate to a change in the state of the chart component. I've tried without using componentWillReceiveProps but it hasn't worked either.
You have two main issues in your code:
1. You should bind your methods
Otherwise, you won't have access to this inside them.
constructor(props) {
...
// Add this to your constructor
this.callChartValue = this.callChartValue.bind(this);
...
}
2. You shouldn't use strings for referencing the methods
render() {
return (
...
// Wrong
<button onClick="callChartValue">call</button>
// Right
<button onClick={this.callChartValue}>call</button>
...
)}
I have a React.Component with render() declared this way:
render(){
return <div>
<button id="butt" onClick={()=> $("#noti").change("test") }>click me</button>
<Notification id="noti" onMounted={() => console.log("test")}/>
</div>
}
And this is my Notification class:
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
message: "place holder",
visible: false
}
}
show(message, duration){
console.log("show")
this.setState({visible: true, message})
setTimeout(() => {
this.setState({visible: false})
}, duration)
}
change(message){
this.setState({message})
}
render() {
const {visible, message} = this.state
return <div>
{visible ? message : ""}
</div>
}
}
As the class name suggests, I am trying to create a simple notification with message. And I want to simply display the notification by calling noti.show(message, duration).
However, when I try to find noti by doing window.noti, $("#noti") and document.findElementById("noti"), they all give me undefined, while noti is displayed properly. And I can find the butt using the code to find noti.
How should I find the noti? I am new to front end so please be a little bit more specific on explaining.
It's not a good idea using JQuery library with Reactjs. instead you can find a appropriate react library for notification or anything else.
Also In React we use ref to to access DOM nodes.
Something like this:
constructor(props) {
super(props);
this.noti = React.createRef();
}
...
<Notification ref={this.noti} onMounted={() => console.log("test")}/>
more info: https://reactjs.org/docs/refs-and-the-dom.html
I have hardcoded the id to 'noti' in the render method. You can also use the prop id in the Notification component.I have remodelled the component so that you can achieve the intended functionality through React way.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messageContent: 'placeholder'
}
}
setMessage = (data) => {
this.setState({messageContent : data});
}
render() {
return (
<div className="App">
<button id='butt' onClick= {() => this.setMessage('test')} />
<Notification message = {this.state.messageContent} />
</div>
);
}
}
class Notification extends React.Component {
render () {
const {message} = this.props;
return (
<div id='noti'>
{message}
</div>
)
}
}
Before beginning: Using id/class to reach DOM nodes is not suggested in React.js, you need to use Ref's. Read more at here.
In your first render method, you give id property to Notification component.
In react.js,
if you pass a property to some component, it becomes a props of that
component. (read more here)
After you give the id to Notification, you need to take and use that specific props in your Notification component.
You see that you inserted a code line super(props) in constructor of Notification? That means, take all the props from super (upper) class and inherit them in this class.
Since id is HTML tag, you can use it like:
class Notification extends React.Component {
constructor(props) {
// inherit all props from upper class
super(props);
this.state = {
message: "place holder",
visible: false,
// you can reach all props with using this.props
// we took id props and assign it to some property in component state
id: this.props.id
}
}
show(message, duration){
// code..
}
change(message){
// code..
}
render() {
const {visible, message, id} = this.state
// give that id to div tag
return <div id={id}>
{message}
</div>
}
}
You can't pass id/class to a React Component as you would declare them in your normal HTML. any property when passed to a React Component becomes a props of that component which you have to use in the component class/function.
render() {
const {visible, message} = this.state
// give your id props to div tag as id attr
return <div id={this.props.id}>
{message}
</div>
}
This answer does not provide the exact answer about selecting a component as you want. I'm providing this answer so you can see other alternatives (more React way maybe) and improve it according to your needs.
class App extends React.Component {
state = {
isNotiVisible: false
};
handleClick = () => this.setState({ isNotiVisible: true });
render() {
return (
<div>
<button onClick={this.handleClick}>Show Noti</button>
{this.state.isNotiVisible && (
<Noti duration={2000} message="This is a simple notification." />
)}
</div>
);
}
}
class Noti extends React.Component {
state = {
visible: true
};
componentDidMount() {
setTimeout(() => this.setState({ visible: false }), this.props.duration);
}
render() {
return this.state.visible && <div>{this.props.message}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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="root" />
I'm having trouble getting event.key from input onKeyPress event, getting this error when i type in the input field - TypeError: Cannot read property 'key' of undefined
The plan is to update parent state when enter is pressed in child input component.
class ToDoList extends Component {
constructor(props) {
super(props);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.state = {
todoArr: [],
};
}
handleKeyPress(e){
console.log(e.key);
}
render(){
return(
<div className="wrapper">
<p className = "title">todos</p>
<InputField testProp={this.handleKeyPress}></InputField>
<div className = "filter-wrap">
<ItemsLeft></ItemsLeft>
<Filter></Filter>
<ClearCompleted></ClearCompleted>
</div>
</div>
)
}
}
class InputField extends Component {
render(){
return(
<input type="text" className="input-field" onKeyPress = {()=> this.props.testProp()}/>
)
}
}
You break the code in the children by doing () => this.props.testProp() if you want this way you should have pass the args like this (e) => this.props.testProp(e)
This is how I would have make it
class ToDoList extends Component {
constructor(props) {
super(props);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.state = {
todoArr: []
};
}
handleKeyPress(e) {
console.log(e.key);
}
render() {
return (
<div className="wrapper">
<p className="title">todos</p>
<InputField testProp={this.handleKeyPress} />
</div>
);
}
}
class InputField extends Component {
render() {
return (
<input
type="text"
className="input-field"
onKeyPress={this.props.testProp}
/>
);
}
}
You can test it here :) https://codesandbox.io/s/6n2jp8yzy3
If you want to see the console you have a button bottom left for open it.
By doing this the callback receive all the args provide by the input, so you don't need to write it by your own.
Learning React so might be a bit nooby question. Consider this code:
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle(on) {
this.setState({
isOn: on
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: false
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
});
this.props.handleToggle(on);
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
ReactDOM.render(<Application />, document.getElementById('app'));
As you can see currently when the button inside the Container is clicked handleButtonToggle() is fired which changes the state of the button itself and then calls a function to change the state of the parent Container. Is this the React way to do it? At the moment Container state is changed when the function handleButtonToggle is fired. Ideally I would want Container state to be dependent on MyButton state directly (cuz maybe in future there will be ways to set button state other than through handleButtonToggle, and I don't want to manually call this.props.handleToggle every time the state changes.). In other words is there a way to do something like this.props.handleToggle(this.state.pressed) in the button component when its state changes.
Codepen
After reviewing the code, a better way to write the Button component is to make it a controlled component. If the container component requires to maintain the pressed state, a controlled button component would receive the pressed state from the container component as props.
<MyButton pressed={this.state.pressed} onToggle={this.handleToggle} />
And the render method of the Button component should be:
render() {
return (
<div>
<button onClick={this.props.onToggle}>
{this.props.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
The actual button toggle will be done in the handleToggle method of the container component:
handleButtonToggle() {
let { pressed } = this.state;
pressed = !pressed;
this.setState({
pressed
});
}
You can either pass a callback as the second argument to setState or implement componentDidUpdate on your component to wait for state changes. The smallest change would be the former:
handleButtonToggle() {
const on = !this.state.pressed
this.setState({
pressed: on
}, () => {
this.props.handleToggle(on);
});
}
or with componentDidUpdate:
componentDidUpdate(prevProps, prevState) {
if (prevState.on !== this.state.on) {
this.props.handleToggle(this.state.on);
}
}
Here is a working codepen: https://codepen.io/damien-monni/pen/XRwewV.
I tried to keep as much as I can of your code so you can focus on really needed changes.
You need to create a controlled component. The state of your button will be stored in the container and pass by props to the child MyButton component.
/*
* A simple React component
*/
class Application extends React.Component {
render() {
return <Container />
}
}
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
isOn: false
}
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() {
this.setState({
isOn: !this.state.isOn
});
}
render() {
return (
<div>
<p>
{this.state.isOn ? 'on' : 'off'}
</p>
<MyButton pressed={this.state.isOn} handleToggle={this.handleToggle} />
</div>
);
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
pressed: this.props.pressed
}
this.handleButtonToggle = this.handleButtonToggle.bind(this);
}
handleButtonToggle() {
this.props.handleToggle();
}
componentWillReceiveProps(nextProps) {
if (nextProps.pressed !== this.props.pressed) {
this.setState({ pressed: nextProps.pressed });
}
}
render() {
return (
<div>
<button onClick={this.handleButtonToggle}>
{this.state.pressed ? "pressed" : "depressed"}
</button>
</div>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
You need to do it because you want to share a state between two of your components. This is documented here.
I let you look at the codepen and ask your questions about it. ;)
I'm using React and when a user clicks on the <li> tag, the popup method is fired, but the component inside the method is not shown, the popup component does not get fired, why is that?
export default class Main extends React.Component {
constructor(props) {
super(props);
}
popup(value) {
console.log('fired ok');
//call popup component
<Popup value={value} />
}
render() {
return (
<ul>
<li key={0} onClick={() => this.popup(value)} />
</ul>
)
}
}
export default class Popup extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('this is not fired');
const { value } = this.props;
return (
<div class="popup">
<p>{value}</p>
</div>
)
}
}
You would need to actually render the Popup element, something along the lines of:
export default class Main extends React.Component {
constructor(props) {
super(props);
// save the popup state
this.state = {
visible: false, // initially set it to be hidden
value: '' // and its content to be empty
};
}
popup(value) {
console.log('fired ok');
this.setState({
visible: true, // set it to be visible
value: value // and its content to be the value
})
}
render() {
// conditionally render the popup element based on current state
const popup = (this.state.visible ? <Popup value={this.state.value} /> : null);
return (
<ul>
{popup}
<li key={0} onClick={() => this.popup('Hello World')}>Click Me!</li>
</ul>
)
}
}
Here's a fiddle of it in action. Click on the black "Click Me!" text.
I hope that helps!