ReactJS - Components - Best Practices - javascript

I'm relative new to react and I need to create a character counter component (CharacterCounter) for a input field.
This question is not specific to this component (character counter) - but is a generic question about components best practices in general.
I can implement in two ways (as far I know - if there are better ways I'm happy to hear about it):
1) Wrapping the component and have the input field as a child - The component will insert the span (for showing the counter) after the input field
<CharacterCounter maxLength={50}>
<input type="text">
</CharacterCounter>
and
const input = this.container.firstChild
input.addEventListener('keyup', function() { ... });
advantage: I can have multiple components for the same input - if I need extra functionality (components) for this input.
disadvantage: If the input for some reason stop being the first child of this component - stop working/ fragile
2) To create a generic component which will render the input and the counter on the render() function
like:
<CharacterCounter />
render() {
return (
<input type="text">
<span>{this.state.count}</span>
)
advantage: Not fragile - not relying on the first child
disadvantage: Not sure is possible to have other component for the same input - let's say I need another component for tracking every time the user type/ or focus/ or blur the field
What is the best practices?

Surely the second approach is better as it is not directly interfering with DOM elements.
If you wanted to have access to DOM elements, still it's better to use refs.
disadvantage: Not sure is possible to have other component for the
same input - let's say I need another component for tracking every
time the user type/ or focus/ or blur the field
You will get around that easily just with props.

You could use second approach with components with state and then use composition to extend that component or create more “special cases” of that component.
let {Component} = React;
class Input extends Component {
constructor(props) {
super(props);
this.state = {count: 0}
}
render() {
return <div>
<input
{...this.props}
onChange={() => {
let count = this.refs.inputCount.value.length;
this.setState({count})
}}
type="text"
maxLength="50"
ref="inputCount" />
<span> {this.state.count} / 50</span>
</div>
}
}
class FocusInput extends Component {
render() {
return <Input
onFocus={() => {
console.log('Focus')
}} />
}
}
class App extends Component {
render() {
return <div>
<Input />
<FocusInput />
</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<script src="https://unpkg.com/react#16.1.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.1.0/umd/react-dom.development.js"></script>
<div id="app"></div>

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

How can React add and remove work together?

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.

Implement autosave on input tag in React.js

I'm trying to implement the Google Tasks Add Task feature (See pic for reference). Basically, when you click on Add Task button, it opens an input field which autosaves as you type. I'm trying to implement this in React.js.
class App extends Component {
state = {
showInput: false,
addTaskInput: ''
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
render() {
const {showInput, addTaskInput} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
/> }
<TodoList addItem={}/>
</div>
);
}
}
Here is my code. App is my main component. TodoList is the component which has the whole list of Todos. What I am going to do is:
When I type something in the input, it fires onChange and sets the state. Inside onChange I would also change the addItem prop which would re-render TaskList. This doesn't seem very optimal because of unnecessary re-renders. I'm thinking of changing addItem prop when focus on input is removed.
How do I go about doing the latter? Alternative approaches are welcome.
I would say it depends on how much your app is doing; if it's just a simple app saving onChange is not bad, but if a lot else is going on, yeah you should optimise.
onBlur
mix keystroke and debounce (my favorite)
when input is more than a set length

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