unable to find a React.Component by id - javascript

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" />

Related

Troubles with state

I'm just started to learn react, and i have a question
Well, i can impact on state from one component to another. But can i do it in reverse?
Here's what i mean:
import React from 'react';
import Butt from './Button';
class Checkbox extends React.Component {
constructor(props) {
super();
}
render() {
return (
<div>
<Butt arg={13} />
</div>
);
}
}
export default Checkbox;
import React from 'react';
class Butt extends React.Component {
constructor(props) {
super();
this.state = {
s1: props.arg,
};
}
add = () => {
let val = this.state.s1;
val++;
this.setState({ s1: val });
};
render() {
return (
<div>
<label>
<label>
<button onClick={this.add}>add</button>
<div>{this.state.s1}</div>
</label>
</label>
</div>
);
}
}
export default Butt;
Sorry for my silly question. Thanks in advance :)
I am not sure about your question, but in react, there is a one-way flow (from parent to child) for transferring information (props, states, or ...). If you want to have access to states everywhere or set them in each direction you should use Redux or context or any other state management.
You're updating the Butt state from inside Butt so this will work fine. It won't change the value of this.props.arg though, if that's what you're asking.
Props are always non-mutable.
What you can do is have two components share the state of their parent...
class Parent extends React.Component {
state = {
val = 0
}
render () {
return (
<>
<Child1
val={this.state.val}
onChange={newVal => this.setState({ val: newVal })}
/>
<Child2
val={this.state.val}
onChange={newVal => this.setState({ val: newVal })}
/>
</>
)
}
}
Then inside the child components pass the updated value to onChange...
class Child1 extends React.Component {
handleChange() {
this.props.onChange(this.props.val + 1)
}
render() {
return (
<Button onClick={() => this.handleChange()}>
Update value
</Button>
)
}
}
This way you're just passing a new value from Child to Parent and letting Parent decide what to do with it.
Whether Child1 or Child2 sends the new value, both children will get updated when Parent calls this.setState({ val: newVal }) and changes this.state.val.

Updating child Components with state change in Reactjs

So I know this question has been asked a couple of times and the general concession is that props cant be changed when it has already passed down to a child. The situation I have here is that basically i have a different onClick function in a different file that updates the the id="movie-header" with an innerHTML, the DOMSubtreeModified and componentDidUpdatedetects the change and pass down the new props to Child "Ebay".
So the question here is how do I get the Ebay component to update its state and make use of the new value with every change to the state in the moviemodalwindow(the parent of the Ebay)
MovieModalWindow.js
import React from "react";
import "../MovieGo.css";
import Ebay from "../Store/Ebay";
class MovieModalWindow extends React.Component {
constructor() {
super();
this.state = {
name: 1
};
}
componentDidMount() {
var element = document.getElementById("movie-header");
element.addEventListener("DOMSubtreeModified", this.myFunction(element));
var name = this.state.name + 1;
this.setState({ name: [...this.state.name, name] });
}
myFunction = input => event => {
this.setState({ name: input.innerHTML });
};
componentDidUpdate(prevProps, prevState) {
if (prevState.name != this.state.name) {
window.localStorage.setItem("keyword", this.state.name);
}
}
render() {
return (
<div id="myModal" class="modal">
<div class="modal-content">
<span onClick={onClose} class="close">
×
</span>
<h1 id="movie-header" />
<div className="middle-window">
<div className="left">
<Ebay id="ebay" keyword={this.state.name} />
</div>
</div>
<h3>PLOT</h3>
<p id="moviedetails" />
</div>
</div>
);
}
}
export default MovieModalWindow;
Ebay.js File
import React from "react"
class Ebay extends React.Component{
constructor(){
super();
this.state={
data:[],
}
}
componentWillUpdate(prevProps, prevState){
if (prevProps.keywords!=this.props.keywords){
console.log(window.localStorage.getItem("keyword"))
}
render(){
const{newInput} =this.props
return(
<div>
</div>
)
}
}
export default Ebay
I'm unsure if I'm answering the question you're asking, so apologies if this isn't what you're asking.
Step 1. Make Ebay's prop's change when you need this update to happen. (I think you stated you already have this occurring?)
Step 2: Make Ebay's state update when the props change. Here you can just watch for prop changes with componentWillReceiveProps and update the state accordingly.
class Ebay extends React.Component {
constructor() {
super();
this.state = { data: [] };
}
componentWillRecieveProps(nextProps) {
if (nextProps.keyword !== this.props.keyword) {
this.setState({ data: ['something new'] });
}
}
render() { ... }
}

React how to track event through components

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>
);
}
}

How can I use 'this' in defaultProps React.Component class syntax?

In the past I was able to set a default prop that used this like so...
let MyButton = React.createClass({
getDefaultProps() {
return {
buttonRef: (ref) => this.button = ref
};
},
But now that I'm using JS classes MyButton looks like this...
class MyButton extends React.Component {
static defaultProps = {
buttonRef: (ref) => this.button = ref
};
And I get an error saying that this is undefined.
What do I need to do to be able to set default props that use this?
EDIT: Add some context
Setting a default buttonRef prop allowed me to use the ref in the MyButton component but also always be able to pass in a custom ref if a parent component needs to access the MyButton DOM node.
class MyButton extends React.Component {
static defaultProps = {
buttonRef: (ref) => this.button = ref
};
componentDidMount() {
Ladda.bind(this.button);
}
render() {
return (
<button
ref={(ref) => this.button = this.props.buttonRef(ref)}
onClick={this.props.handleClick}
>
{this.props.buttonText}
</button>
);
}
}
So then my button can always get hooked in to Ladda: Ladda.bind(this.button)
And if I need to access that button's DOM node in a parent component I can do so by passing in buttonRef as a prop like...
class MouseOverButton extends React.Component {
componentDidMount() {
this.mouseEnterButton.addEventListener("mouseover", doSomething(event));
}
render() {
return (
<div>
<MyButton
buttonRef={(ref) => this.mouseEnterButton = ref}
/>
</div>
);
}
}
EDIT: Apparently my simplified example doesn't illustrate the point well enough so I can come up with a more practical example or y'all can just answer the original question: What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
Where this convention of having a defaultProp for a specific element's ref has been useful is when using a HOC that hooks a nested component into some 3rd party API. I have an AddressSearch HOC that takes a node via a function passed to the wrapped component. Then it uses that node to hook it up with Google's Places API.
So I've got my addAddressSearch(component) function from my HOC. It adds the functions needed to hook up the Google places API. But for Google's API to work I need to know what DOM node I'm working with. So I pass my Input component an inputRef that gives my AddressSearchInput access to the appropriate node.
class AddressSearchInput extends React.Component {
static defaultProps = {
inputRef: (ref) => this.addressSearchInput = ref
};
componentDidMount() {
let node = this.addressSearchInput;
this.props.mountAddressSearch(node);
}
render() {
return (
<div>
<Input
inputAttributes={inputAttributes}
inputRef={(ref) => this.addressSearchInput = this.props.inputRef(ref)}
labelText={<span>Event address or venue name</span>}
labelClassName={labelClassName}
/>
</div>
);
}
}
AddressSearchInput = addAddressSearch(AddressSearchInput);
module.exports = AddressSearchInput;
// Here's the Input component if that helps complete the picture here
class Input extends React.Component {
render() {
return (
<div>
<Label>{this.props.labelText}</Label>
<HelperText text={this.props.inputHelperText} />
<input
{...this.props.inputAttributes}
ref={this.props.inputRef}
></input>
</div>
);
}
}
So now when I want to use my AddressSearchInput in a parent component that needs to add an eventListener to the relevant node I can just pass AddressSearchInput an inputRef prop.
class VenueStreetAddress extends React.Component {
componentDidMount() {
let node = this.venueStreetAddressInput;
this.props.mountValidateOnBlur(node, venueValidationsArray);
},
render() {
return (
<div>
<AddressSearchInput
inputRef={(ref) => this.venueStreetAddressInput = ref}
hasError={this.props.hasError}
/>
{this.props.errorMessageComponent}
</div>
);
}
}
And I can use AddressSearchInput all over the place and it doesn't break anything.
class UserStreetAddress extends React.Component {
componentDidMount() {
let node = this.userStreetAddressInput;
this.props.mountValidateOnBlur(node, userValidationsArray);
},
render() {
return (
<div>
<AddressSearchInput
inputRef={(ref) => this.userStreetAddressInput = ref}
hasError={this.props.hasError}
/>
{this.props.errorMessageComponent}
</div>
);
}
}
Maybe this way is convoluted and wrong but I don't have the time to figure out another way to do it on my own. So either point me to a tutorial(s) on the best way to hook into 3rd party APIs and add dynamic form validation without using refs or answer my original question which is...
What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
EDIT: In attempting to explain my use case I had the idea to make my defaultProps look like this...
static defaultProps = {
inputRef: (ref) => ref
};
which seems to be working without error.
In any case, the original question still stands. What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
This really should be a method, not a property.
class MyButton extends React.Component {
setButtonRef (ref) {
this.button = ref;
}
componentDidMount() {
Ladda.bind(this.button);
}
render() {
return (
<button
ref={ ref => this.setButtonRef(ref) }
onClick={this.props.handleClick}
>
{this.props.buttonText}
</button>
);
}
}
If you want a ref to the button, bind a variable at the class level and assign it to that class variable. Example:
export default class MyComponent extends Component {
componentDidMount() {
// button will be available as `this.button`
}
button = null; // initialize to null
render() {
return (
<div>
<Button ref={e => { this.button = e; }} />
</div>
);
}
}
Since a static property has no knowledge of class instances, if it's strictly necessary to make a static method aware of a given instance, the only way would be to pass to the static method the instance as an argument:
<button
ref={(ref) => this.button = this.props.buttonRef(this, ref)}
onClick={this.props.handleClick}
>
And in your defaultProp, use the instance:
static defaultProps = {
buttonRef: (instance, ref) => instance.button = ref
};

Tie an action to a state change in pure React

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. ;)

Categories

Resources