How can React add and remove work together? - javascript

I am new to react and am trying to toggle a body class using two different buttons. One is supposed to add a class using an onClick event and the other is supposed to remove the class. Below is an example of my code.
Right now in the console I can see the event fire twice but the class remains. As I stated I am new to React so I know I may be doing this incorrectly.
bodyFixed() {
document.body.classList.add('body-fixed');
}
bodyRelative() {
document.body.classList.remove('body-fixed');
}

You are trying to modify the dom directly like you would with vanilla js or JQuery, but this is not how react is meant to be used. React creates a virtual dom that you create and manage, and then react handle changing the page for you.
I recommend following a guide like this one to learn basic setup and concepts (skip to the part where he uses JSX).
I can further point you in the right direction if you show your whole component file.

You want to toggle a className prop value in the React way.
The React way is having a state prop and having a handler function that will toggle the state value, rather than manipulating the DOM node directly (the way you're doing it).
I would suggest you to take a look at React Main Concepts: Handling events and later once you feel a little bit more comfortable to read about Virtual DOM and Reconciliation in React.
Here's how can you do it:
const { classNames } = window
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
isToggled: true
}
this.toggleClass = this.toggleClass.bind(this)
}
toggleClass() {
const { isToggled } = this.state
this.setState({
isToggled: !isToggled
})
}
render() {
const { isToggled } = this.state
const className = classNames({
'body-fixed': isToggled
})
return <div className={className}>
<div>Current `className`: <b>{ className }</b></div>
<button onClick={this.toggleClass}>Toggle class</button>
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://unpkg.com/classnames#2.2.6/index.js"></script>
<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="container"></div>

I was able to use the code I listed earlier. I had my onClick events positioned incorrectly. Here is an example of the code I used:
bodyFixed() {
document.body.classList.add('body-fixed');
}
bodyRelative() {
document.body.classList.remove('body-fixed');
}
<Toggle>
{({on, getTogglerProps, setOn, setOff, toggle, showAlert}) =>
<div>
<button className="search-icon-top" {...getTogglerProps()}>{on ? <img className="times" onClick={this.bodyRelative} src={require('img/times.svg')} alt=" " /> : <i className="fa fa-search" onClick={this.bodyFixed}></i>}</button>
<div>
{on ? <TodaySearchBox /> : ''}
</div>
</div>}
</Toggle>
This is just a start for now. Thank you for the input.
EDIT: I am open to suggestions. Like I said I am new to React.

Related

Creating and displaying new elements on event (React.JS)

I've used stack overflow for the last two years since I picked up programming, but I've never actaully asked a question directly. However, recently I've been running into a problem with React which has stumped me for the last week, and it feels like one of those problems a pro will look at and instantly know the answer to.
My code: https://codepen.io/gooeyWolf/pen/xxZGxGq
<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>
<body>
<div id="root"><div id="shit"></div></div>
<script src="https://unpkg.com/react#16.12.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.12.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.8.3/babel.js"></script>
<script type="text/babel">
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: '',};
this.handleChange = this.handleChange.bind(this);
}
NextThing(){
const element = <h1>Hello, world!</h1>;
const divy = document.body.getElementById("div2");
divy.appendChild(element);
//document.body.appendChild(element);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleEnter(event){
if(event.key === 'Enter'){
this.NextThing();
}
}
render() {
return (
<div>
<span>Are you writing for a protagonist, antagonist, love interest, or secondary character? <br /></span>
<input name="flip" id="lucky" value={this.state.value} onChange={this.handleChange} onKeyDown={this.handleEnter} />
<div id="div2"></div>
</div>
);
}
}
//onChange={this.handleChange}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
</script>
</body>
The app Im building is pretty simple. Basically, the app asks you a question, you type in your answer, and it displays a new block of text and a new input box to type your next answer, while leaving previous text on the screen.
So far, when you type something and press enter, the programs handles the enter, and calls another function to display the next block of text.
At first, i tried using return/render statements, but this cause the page to refresh, which would be fine, but the new elements I wanted to display doesn't show up. The React.JS course I'm taking advises against using creatElement statements for every new element required, so I'm working with appendChild, which for some reason this runs into errors.
I feel like there has to be a more simple solution to this, and a more reusable solution too. Ideally, it would be able to handle a refresh and keep the previously added elements.
I wrote this program in python first, with print and input statements, and it was so easy, but I spent the last week trying figure out the right way with React, but all my solutions seem unnecessarily complicated. There has to be a better way...
Thank you guys so much, this helps more than I can express.
Stack Overflow the best (:
In React, the common DOM manipulation functions (appendChild, innerHtml etc) are not the "best" way to do this, there is a more usable solution as you said. What you can do, is take advantage of the way JavaSript expressions can be renderered inside the HTML (That is, those that are inside with {{variable}} with curly braces) and combine it with this.state.
First in this.state, define an array div2Children which will contain all the text that is typed into the input
// This is inside the constructor
this.state = {value: '', div2Children: []};
Then you can map through each element and convert them in HTML.
render() {
return (
<div>
<span>Are you writing for a protagonist, antagonist, love interest, or secondary character? <br /></span>
<input name="flip" id="lucky" value={this.state.value} onChange={this.handleChange} onKeyDown={this.handleEnter} />
<div id="div2">
{/* when the array changes this page will also change the HTML */}
{this.state.div2Children.map(child => (
<h1>{child}</h1>
))}
</div>
</div>
);
}
Then finally in your NextThing method you'll have to change with this.setState the div2Children array to have all the previous elements but also your newly typed (appending the last element but creating a new array)
NextThing(){
this.setState({div2Children : [...this.state.div2Children, this.state.value]});
}
Note that you will also have to bind NextThing and handleChange. Here is a working version
<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>
<body>
<div id="root"><div id="shit"></div></div>
<script src="https://unpkg.com/react#16.12.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.12.0/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.8.3/babel.js"></script>
<script type="text/babel">
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: '', div2Children: []};
// Bind all the functions
this.NextThing = this.NextThing.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleEnter = this.handleEnter.bind(this);
}
NextThing(){
this.setState({div2Children : [...this.state.div2Children, this.state.value]});
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleEnter(event){
if(event.key === 'Enter'){
this.NextThing();
}
}
render() {
return (
<div>
<span>Are you writing for a protagonist, antagonist, love interest, or secondary character? <br /></span>
<input name="flip" id="lucky" value={this.state.value} onChange={this.handleChange} onKeyDown={this.handleEnter} />
<div id="div2">
{this.state.div2Children.map((child, index) => (
<h1 key={index}>{child}</h1>
))}
</div>
</div>
);
}
}
//onChange={this.handleChange}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
</script>
</body>
Also notice here that I am also setting a key attribute in the snippet above. You can find more about that here, I just provided a simple solution which should be fine.
Firstly, you can't access to the DOM elements like that in React. Check the refs react API. Anyway, I think you should reverse your reflection here and avoid those dom-manipulation (if you can).
The data should be enough to display questions and answers.
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {
step: 1,
values: [''],
questions: [
{
id: 1,
label: 'Are you writing for a protagonist, antagonist, love interest, or secondary character?',
},
{
id: 2,
label: 'Are you happy?',
}
// other questions
]};
}
NextThing = (next) => {
this.setState((prev) => ({step: prev.step+1, values: [...prev.values, '']}));
}
handleChange = (index, event) => {
event.persist()
this.setState(state => {
const values = state.values.map((item, j) => {
if (j === index) {
return event.target.value;
} else {
return item;
}
});
return {
values,
};
});
}
handleEnter = (index, event) => {
if(event.key === 'Enter' && this.state.step < this.state.questions.length){
this.NextThing(index);
}
}
render() {
const { questions, step, values } = this.state;
return (
<div>
{questions.slice(0, step).map((question, index) => (
<div key={question.id}>
<span>{question.label}<br /></span>
<input
name={"answer"+question.id}
id={"answer"+question.id}
value={values[index]}
onChange={e => this.handleChange(index, e)}
onKeyDown={e => this.handleEnter(index, e)} />
</div>
))}
</div>
);
}
}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
Let me know if I misunderstood something or if it helps :D
As others wrote, you shouldn't try to manipulate DOM like regular javascript in React.
So; we shouldnt manipulate elements like regular js, but what we can do is we can change stuff inside HTML with Refs and States. You can use ref's to give an element some kind of "id" in React way, and change its properties. State is one of the key elements of React and its quite powerful.
https://reactjs.org/docs/state-and-lifecycle.html
We can toggle on and off divs, inputs and other elements visibility, innerTexts and etc.
https://reactjs.org/docs/hooks-state.html
I will use a State Hook to change displaying of a div in here:
const [testState, setTestState] = useState(false)
...
return (
<>
<h1>Example Page</h1>
{testState ? <input placeholder="Example Input" /> : null}
</>
)
If my testState is true; an input box will show up; and if its false nothing will. We can use setTestState(true) -or false- now somewhere in our code to change visibility rendered html block.
Now, not sure if i understood your problem and phyton code correctly (i probably didnt), i quickly made a demo with using state's to toggle an elements visibility.
https://codesandbox.io/s/zealous-taussig-0ctif

Is value attribute of input tag a part of DOM?

I have some default value, that should be passed to value attribute of input, so this will be controlled component. What I need to know, where I should initialize state, in constructor enter code here or componentDidMount? I find, that componentDidMount is the right place for initialization that requires DOM nodes. So that's why I ask about value attribute.
as you said componentDidMount is the right place for initialization but exactly for the requirement that needs DOM node's measurements like width or height in browser after render (here you can use element's width for some purpose).
so in your case (i mean input), there is no need to set value in componentDidMount.
hence you can simply use constructor that's enough.
Hope this helps.
Use ref to get value of <input/>, update state and initialize this.inputRef.current.value. This is working solution of question,
class App extends React.Component{
constructor(props){
super(props);
this.inputRef = React.createRef();
this.state = {
inputValue: 'This is App component'
}
}
componentDidMount(){
this.inputRef.current.value = this.state.inputValue;
}
keypressHandler = (event) => {
if(event.key === 'Enter')
this.setState({inputValue: this.inputRef.current.value});
}
render() {
return (
<div>
<label>Type text and press enter</label><br/>
<input type='text' ref={this.inputRef} onKeyPress={(event) => this.keypressHandler(event)}/>
<p>{this.state.inputValue}</p>
</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' />

event.stopPropagation() fails on ReactJS Component inside a native HTMLElement

My particular use case of React is thus:
I wish to add a small React Component to a card that is an existing, fully-functional HTML element, per all the cards on the page. This React Component shall serve to implement a new feature on those cards : reverting changes.
The HTML (well, the MVCE version of it)
is something like this:
<div id="some-id" class="card float-sm-left menu-visual-card " onclick="(function(event) { console.log('I got clicked, and a modal will spawn' ) })(event)">
<div class=card-block>
<h5 class="card-title format-text">Some title</h5>
<!-- some business elements here -->
</div>
<!-- card footer -->
<div class=customized-indicator-react></div>
</div>
The React Component
in its tl;dr version is the following:
class CustomizedIndicatorComponent extends React.Component {
constructor(props) {
super(props)
// business logic
let active = this.props.active
this.state = {
active : active
}
}
toggleActive = () => {
this.setState({
...this.state,
active : !this.state.active
})
}
// setup
componentDidMount() {
// here's where I tried to add a jQuery onclick listener to stop propagation, only to have the React Component listener get stopped
}
// teardown
componentWillUnmount() {
console.log("CustomizedIndicatorComponent destroyed!")
}
// the UI logic
render() {
if (this.state.active) {
return (
<div>
<div
className="badge badge-sm badge-info float-sm-left customized"
style={{marginRight:"10px"}}
>Customized</div>
<div
onClick={(e) => {
e.stopPropagation()
this.toggleActive()
}}
title="Click to undo customizations">
<i className="fa fa-undo" aria-hidden="true"></i>
</div>
</div>
)
}
return <div />
}
}
What happens when you run this?
When I run this, it renders. However, when I click the widget to "de-activate" the element, the container's event-handler still fires!!
I know there is a slew of internet questions about this issue or something close to it, but none of the ones I could find seem to be about this exact use case.
Also, adding an event listener in componentDidMount doesn't work, as that prevents anything from firing!
Is there any way I can make this work without wasting developer-hours refactoring everything including the parent HTMLElements?
A "hacky" way you may consider is to get the parent's id from inside the React component and disable the click event from there.
If id could not be passed as a property to the React component, you can try using ReactDOM.findDOMNode(this).parentNode.getAttribute("id") to get it and then disable the event using:
document.getElementById(id).style.pointerEvents = 'none';

Set dynamic state name in React.js

I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen

React - passing ref to sibling

I need to have 2 sibling components, and 1 of them has to have a reference to another - is that possible?
I tried this:
<button ref="btn">ok</button>
<PopupComponent triggerButton={this.refs.btn} />
And even this
<button ref="btn">ok</button>
<PopupComponent getTriggerButton={() => this.refs.btn} />
But I get undefined in both situations. Any ideas?
Note: a parent component will not work for me, because I need to have multiple sets of those, like
<button />
<PopupComponent />
<button />
<PopupComponent />
<button />
<PopupComponent />
NOT like this:
<div>
<button />
<PopupComponent />
</div>
<div>
<button />
<PopupComponent />
</div>
<div>
<button />
<PopupComponent />
</div>
Think this question is best answered by the docs:
If you have not programmed several apps with React, your first
inclination is usually going to be to try to use refs to "make things
happen" in your app. If this is the case, take a moment and think more
critically about where state should be owned in the component
hierarchy. Often, it becomes clear that the proper place to "own" that
state is at a higher level in the hierarchy. Placing the state there
often eliminates any desire to use refs to "make things happen" –
instead, the data flow will usually accomplish your goal.
Not sure exactly what you are trying to do, but my hunch is a parent component and passing props is what you really want here.
I completely agree with the quote Mark McKelvy has provided. What you are trying to achieve is considered an anti-pattern in React.
I'll add that creating a parent component doesn't necessarily means it has to be a direct parent, you can create a parent component further up the chain, in which you can render an array of all your children components together, having the logic to coordinate between all the children (or pairs of children according to your example) sit inside your parent.
I created a rough example of the concept which should do the trick:
class A extends React.Component {
onClick(key) {
alert(this.refs[key].refs.main.innerText);
}
render() {
var children = [];
for (var i = 0; i < 5; i++)
children.push.apply(children, this.renderPair(i));
return (
<div>
{children}
</div>
);
}
renderPair(key) {
return [
<B ref={'B' + key} key={'B' + key} onClick={this.onClick.bind(this, 'C' + key)}/>,
<C ref={'C' + key} key={'C' + key} onClick={this.onClick.bind(this, 'B' + key)}/>
];
}
}
class B extends React.Component {
render() {
return <p ref="main" onClick={this.props.onClick}>B</p>;
}
}
class C extends React.Component {
render() {
return <p ref="main" onClick={this.props.onClick}>C</p>;
}
}
React.render(<A/>, document.getElementById('container'));
And any state you need to save for all your children, you do in the common parent. I really hope this helps.
The following code helps me to setup communication between two siblings. The setup is done in their parent during render() and componentDidMount() calls. Hope it helps.
class App extends React.Component<IAppProps, IAppState> {
private _navigationPanel: NavigationPanel;
private _mapPanel: MapPanel;
constructor() {
super();
this.state = {};
}
// `componentDidMount()` is called by ReactJS after `render()`
componentDidMount() {
// Pass _mapPanel to _navigationPanel
// It will allow _navigationPanel to call _mapPanel directly
this._navigationPanel.setMapPanel(this._mapPanel);
}
render() {
return (
<div id="appDiv" style={divStyle}>
// `ref=` helps to get reference to a child during rendering
<NavigationPanel ref={(child) => { this._navigationPanel = child; }} />
<MapPanel ref={(child) => { this._mapPanel = child; }} />
</div>
);
}
}
special-props
Special Props Warning
Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.
For instance, attempting to access this.props.key from a component (eg. the render function) is not defined. If you need to access the same value within the child component, you should pass it as a different prop (ex: ). While this may seem redundant, it's important to separate app logic from reconciling hints.

Categories