react-dom: Incrementally change opacity of an element on click - reactjs

I'd like to be able to increase opacity of a DOM element on click of a button.
Wrapping my head around React's concepts, I think this should be done through using state and a functional setState() function.
However, I keep running into an error with the following code:
import React, { Component } from 'react'
class ThoughtfulThoughts extends Component {
state = {
opacity: 0.4,
thoughts:
[
"live and let leave",
"just pee yourself",
"who knows what 'laf' is?"
]
}
makeAppear = () => {
// TODO: this causes a "RangeError: Maximum call stack size exceeded"
this.setState(prevState => ({
opacity: prevState.opacity + 0.2
}))
}
render() {
return (
<div className="thoughtful-thoughts">
<div className="current-thought" style={{opacity: this.state.opacity}}>
{this.state.thoughts.map((thought, i) => (<p>{thought}</p>))}
</div>
<button onClick={this.makeAppear()}>Think for me</button>
</div>
)
}
}
export default ThoughtfulThoughts
I don't want to use jQuery, nor direct DOM manipulation, nor
CSS transitions, but would like to do this just in JS in "the React way".
Any pointers would be greatly appreciated!

You have a couple of minor issues in your code:
On your button component, change onClick={this.makeAppear()} into onClick={this.makeAppear}.
Nicely done on using the function approach instead of the object approach for updating a state variable based on a previous state variable. However, you have a minor syntax. Either do:
this.setState(prevState => (
{opacity: prevState.opacity + 0.2}
));
or
this.setState(prevState => {
return {opacity: prevState.opacity + 0.2}
});
whichever you prefer.
Add a key property to each item you return from your map(): So basically:
{this.state.thoughts.map((thought, i) => (<p key={i}>{thought}</p>))}
You can probably use i safely as the key here because the order of the items in thoughts array will remain static. For more info about keys and how to use them properly, take a look here.
Demo:
class ThoughtfulThoughts extends React.Component {
constructor() {
super();
this.state = {
opacity: 0.4,
thoughts:
[
"live and let leave",
"just pee yourself",
"who knows what 'laf' is?"
]
}
}
makeAppear = () => {
this.setState(
prevState => ({opacity: prevState.opacity + 0.2})
);
}
render() {
return (
<div className="thoughtful-thoughts">
<div className="current-thought" style={{opacity: this.state.opacity}}>
{this.state.thoughts.map((thought, i) => <p key={i}>{thought}</p>)}
</div>
<button onClick={this.makeAppear}>Think for me</button>
</div>
);
}
}
ReactDOM.render(<ThoughtfulThoughts />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Related

React how to set all remaining sibling's state to false whenever one sibling's state is true

Problem Description
I am trying to find a way to, whenever an arrow is set to rotate, reset to the initial position the remaining arrows.
Given :
const dropDownImage = 'https://i.postimg.cc/k57Q5cNL/down-arrow.png';
function App() {
const information = ['a' ,'b', 'c'];
return (
<p>
{information.map((value, index) => (
<Child index={index} key={index}/>
))}
</p>
)
}
const Child = ({information, index, key}) => {
const [dropDown, setDropDown] = React.useState(false);
return (
<img className={dropDown ? 'dropArrow doRotate' : 'dropArrow'} src={dropDownImage} width="50" width="50"
onClick={
()=>{setDropDown(!dropDown)}
}
/>
)
}
ReactDOM.render(<App />, document.querySelector("#app"));
.dropArrow {
position: relative;
transition: .25s;
}
.doRotate {
position:relative;
transition: 1s;
animation-fill-mode: forwards !important;
animation: rotate 1s;
}
#keyframes rotate {
100% {transform: rotate(180deg);}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
with JSFiddle Link Here
Restrictions
This is the structure of my larger project, so I can't change the modularization presented here.
What I have tried
I tried setting a hook in the parent component const [resetOthers, setResetOthers] = useState(null), pass the setter function as a prop to the child and trigger setResetOthers(index) from the child whenever setDropDown(!dropDown) is called to rotate the arrow. I then added the resetOthers as a key and made it such that an arrow animates only if it corresponds to the resetOthers index, but that didn't work. My arrows just multiplied on every click.
I tried migrating my setDropDown hook to the parent and handling the logic from there, but then I would need 3 different hooks and it would defeat the purpose of making a child in the first place.
Does anyone know how I can solve this problem ?
Manage the state of the children in the parent by using useState() to hold the currently selected item. Whenever an item is clicked, assign it's value to the state, or remove it if it's already selected:
const dropDownImage = 'https://i.postimg.cc/k57Q5cNL/down-arrow.png';
const App = ({ items }) => {
const [selected, setSelected] = React.useState(null);
return (
<p>
{information.map(value => (
<Child
key={value}
onClick={() => setSelected(s => s === value ? null : value)}
selected={selected === value} />
))}
</p>
)
}
const Child = ({ selected, onClick }) => (
<img className={selected ? 'dropArrow doRotate' : 'dropArrow'} src={dropDownImage}
width="50"
width="50"
onClick={onClick}
/>
)
const information = ['a' ,'b', 'c'];
ReactDOM.render(<App items={information} />, document.querySelector("#app"));
.dropArrow {
position: relative;
transition: transform 1s;
}
.doRotate {
position: relative;
transform: rotate(180deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
A classic case of needing to "lift up state". In React, state should live at the lowest common denominator above which all components will need access to it. In other words, if multiple components will need to read (or write!) to a given piece of state, it should live one level (or more, but as few as possible) above those components that will need access.
In your case you should be tracking your "dropdown" state not in each child component, but in the App component. Then, the App component can pass that state to each child and provide a mechanism to modify it.
First let's refactor so that dropdown active state is kept in App instead of each child:
const dropDownImage = 'https://i.postimg.cc/k57Q5cNL/down-arrow.png';
function App() {
const information = ['a' ,'b', 'c'];
// Use an array, with each value representing one "dropdown" active state
const [dropDowns, setDropDowns] = React.useState([false, false, false]);
return (
<p>
{information.map((value, index) => (
<Child
key={index}
index={index}
// isActive is passed in as a prop to control active state of each child
isActive={dropDowns[index]}
toggle={
() => {
// the toggle function gets provided to each child,
// and gives a way to update dropDowns state
setDropDowns(oldDropDowns => {
const newDropDowns = [...oldDropDowns];
newDropDowns[index] = !newDropDowns[index];
return newDropDowns;
});
}
} />
))}
</p>
)
}
// now <Child> is completely stateless, its active state is controlled by App
const Child = ({information, index, toggle, isActive}) => {
return (
<img className={isActive ? 'dropArrow doRotate' : 'dropArrow'} src={dropDownImage} width="50" width="50"
onClick={toggle}
/>
)
}
From there it is trivial to modify the toggle callback to update state differently -- say, by resetting all dropdown isActive statuses to false except for the clicked one:
toggle={
() => {
setDropDowns(oldDropDowns => {
const newDropDowns = oldDropDowns.map(ea => false);
newDropDowns[index] = true;
return newDropDowns;
});
}
}
I modified your code, you need to manage the state in the parent component, and update it on each click. In order to be able to switch the state from the child component, you can pass a method from the parent component into the children as a prop. In this case I use that prop alongside the index to properly update the state.
In addition, I also passed the value (true or false), as a prop to orient the chevron correctly.
import React, { Component } from "react";
import "./styles.css";
const dropDownImage = "https://i.postimg.cc/k57Q5cNL/down-arrow.png";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
information: [false, false, false]
};
this.updateInformation = this.updateInformation.bind(this);
}
updateInformation(index) {
let newInformation = this.state.information;
for (let i = 0; i < newInformation.length; ++i)
newInformation[i] = i == index ? true : false;
this.setState({ information: newInformation });
}
render() {
return (
<p>
{this.state.information.map((value, index) => (
<Child
index={index}
key={index}
value={value}
updateInformation={this.updateInformation}
/>
))}
</p>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
}
render() {
return (
<img
className={this.props.value ? "dropArrow doRotate" : "dropArrow"}
src={dropDownImage}
width="50"
width="50"
onClick={() => {
this.props.updateInformation(this.props.index);
}}
/>
);
}
}
I attached you a link to the code sandbox so you can play with it: https://codesandbox.io/s/heuristic-wozniak-q5gq7?file=/src/App.js

Understanding the ReactJS quiz from Solo Learn

Hello so I am trying to practice React form solo learn so I have a quiz that they gave that I do not understand however through guessing I got the answer(i.e "OFF") however I would love to now understand why this answer is the correct answer.
Question:
What will be the value on the button after it is clicked 5 times?
Code below:
function Toggle() {
const [val, setVal] = useState("ON");
function toggle() {
setVal((val == "ON")? "OF":"ON");
}
return <button onClick={toggle}> {val} </button>;
}
As you may know, this is a functional component which displays a button. On clicking the button, the function toggle() is called. This function changes the value of the button from ON to OFF, and vice versa.
The button starts with "ON" and changes to "OFF every time you click on it.
Therefore, after clicking on it 5 times, the result is "OFF".
Look this example which show previous state of the val state. After you click five times the button will be disabled.
I'm not using functional component as stackoverflow doesn't support it.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
val: "ON",
history: []
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
val : (this.state.val == "ON")? "OF":"ON",
history: [...this.state.history, this.state.val]
});
this.s
}
render() {
return <div>
<h3>previous state</h3>
<ul>
{this.state.history.map((h, i) => {
return <li key={i}>{i + 1} val = {h}</li>
})}
</ul>
<button onClick={this.toggle}
disabled={this.state.history.length === 5}>
{this.state.val}
</button>
</div>;
}
}
ReactDOM.render(<Toggle />, 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"></div>

To change color on onclick event using react

I need to change the colour of a div tag which I have designed when it is clicked. I need to change the colour of the div and I need to add a class to the div when it is clicked, which I need answer in react
Like others mentioned, a little more code details would be nice from your side.
One of the ways to achieve what you want: https://jsfiddle.net/hawk939393/Ly0912nf/
class TodoApp extends React.Component {
state = {
divClicked: false
};
getClassname = () => {
return !this.state.divClicked ?
"toDoApp" : "toDoApp-isClicked"
};
render() {
return (
<div
className={this.getClassname()}
onClick={() => this.setState(prevState => ({divClicked: !prevState.divClicked}))}>
Hello world!
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
^ Have a state that tracks whether a div's className has been changed.

styled component computing style but not applying it

Dear genius StackOverflowians,
I am trying to write an app where users can configure questions and answers, along with defining help text for each question. I'm writing this in typescript React - which is handy when you want to define types of answers for questions.
I want to have a button next to the question that shows/hides a styled document. The button looks and works great, but the document that is hidden/shown doesn't get the generated style class that ought to be associated with it.
Here is the functional component to display the help document:
let HelpTextBody = function(props: { helpDocument: DocumentationStore }) {
return (
<div>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};
tok comes from a custom class DocumentationStore that is pretty much a wrapper around markdown-it, a handy js library for working with md files, which I would like my users to write their helptext in (and store it that way).
So I do this (in a different module for DocumentationStore class):
toReallySimple(): MdJson[] {
let bigParsed = this.md_.parse(this.Text, null).filter(
t => return t.type == "inline" || t.type.indexOf("open") > 0
});
Later on, I style HelpTextBody with:
const StyledHelpDocument = styled(HelpTextBody)`
background-color: lightslategray;
`;
Keeping it simple now so I can just see if it's working...
I then include it in a component with the button that I export:
class HelpText extends React.Component<helpProps, helpState> {
constructor(props: helpProps) {
super(props);
this.state = {
hidden: true
};
}
swapHidden() {
this.setState({
hidden: !this.state.hidden
});
}
render() {
if (this.state.hidden) {
return (
<span>
<StyledButton
itemScope={this.state.hidden}
onClick={() => this.swapHidden()}
>
Need Help?
</StyledButton>
</span>
);
} else {
return (
<span>
<StyledButton onClick={() => this.swapHidden()}>
Hide Help
</StyledButton>
<StyledHelpDocument helpDocument={this.props.helpDocument} />
</span>
);
}
}
So I webpack it all and get stuff into the browser, and what I get back is this style tag (after clicking the button), which looks right:
<style data-styled-components="">
/* sc-component-id: sc-bdVaJa */
.sc-bdVaJa {} .gscXTZ{background:red;color:white;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}.iwtdKP{background:white;color:red;font-size:1em;margin:1em;padding:0.25em 1em;border:2px solid red;border-radius:3px;}
/* sc-component-id: sc-bwzfXH */
.sc-bwzfXH {} .hAvMqj{background-color:lightslategray;}</style>
But my html for the document is missing the reference to the class (.hAvMqj I guess?)
<span>
<button class="sc-bdVaJa iwtdKP">Hide Help</button>
<div><p>Here the text is grey</p></div>
<!-- ^This^ is the StyledHelpDocument... no class!-->
</span>
So where am I going wrong? I don't understand why it generates the style, and the component's HTML renders... but the class isn't applied to the component! What do you think?
Your styled-components class isn't being applied because you're styling a custom component, but you haven't included className as a prop. Add className as an optional prop in the component you're styling, and also be sure to apply className somewhere in the render method for that component. For your case, it should be added like so:
let HelpTextBody = function(props: { helpDocument: DocumentationStore, className: string }) {
return (
<div className={props.className}>
{props.helpDocument.toReallySimple().map(tok => {
return React.createElement(tok.tag, null, tok.content);
})}
</div>
);
};

Unable to access Children from json , react-redux

Hi, I'm able to access the contact name Ron and use filter in it. But, I'm unable to access the children contact Alex and use it in filter. I have attached my output screenshot. I need to print children Alex also in it and use filter. Thank you in Advance
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
let arr = [{"id":1,
"number":true,
"location":"2",
"time":11,
"code":1001,
"name":"Ron",
"children":[{
"id":141,
"number":true,
"location":1,
"time":1504969439000,
"code":null,
"name":"Alex"}]}]
const Tab = ({ value }) => <div> {value} </div>;
class App extends React.Component {
constructor() {
super();
this.state ={
search: ''
};
}
updateSearch(event){
this.setState({search: event.target.value.substr(0,20)})
}
render() {
let filteredContacts = arr.filter((item)=>{
return item.name.toLowerCase().indexOf(this.state.search)!==-1;
});
return(<div>
<h1> Contact List </h1>
<input type="text"
value={this.state.search}
placeholder="Search Here"
onChange={this.updateSearch.bind(this)} />
<ul>
{
filteredContacts.map((obj, i) =>
<Tab value={obj.name} key={i} />)
}
</ul>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('container'));
You filtering only goes through the first level of contacts in the array, so any contacts in .children will never be shown or be filtered against.
What you want to do is make an array that contains all the contacts, but have them at the same "bottom" level ([{name: 'Ron'}, {name: 'Alex'}]), so that we can filter on that array instead.
One way of doing this is to first .map over the original array and make sure we get both the "bottom" (parent) contact and the children in one array: (if the ... syntax is new to you, scroll to 'Spread in array literals' here)
const allContacts = originalContacts.map(parentContact => [parentContact, ...parentContact.children])
But that gets us an array of arrays: [[{name: 'Ron'}, {name: 'Alex'}]], which is hard to work with. So we want to make a new array where we have concatenated all the arrays in the allContacts array, into just one. Luckily, there is a way to call the .concat method that takes arguments in an array. It looks a bit odd, but we can do: (more about .apply)
const allContactsFlattened =
Array.prototype.concat.apply([], allContacts);
So now we have what we wanted: a "flat" (one level) array of all the contacts. Your filter method already knows how to handle that, so no further changes has to be made.
To see it in action I included your code with the changes applied (in a slightly different way) + some comments:
let originalContacts = [
{"name":"Ron (parent)", "children":[{"name":"Alex (child)"}, {"name":"Emma (child)"}]},
{"name":"Isa (parent)", "children":[{"name":"Sarah (child)"}, {"name":"Ahmad (child)"}]}
];
const Tab = ({ value }) => <div> {value} </div>;
class App extends React.Component {
constructor() {
super();
this.state ={
search: ''
};
}
updateSearch(event){
this.setState({search: event.target.value.substr(0,20)})
}
render() {
let contactsAndSubContacts =
// flatten the result
Array.prototype.concat.apply(
// transform from parent => [parent, child1, child2 etc..]
[], originalContacts.map(contact => [contact, ...contact.children])
);
let filteredContacts = contactsAndSubContacts.filter((item)=>{
// NOTE: you probably want to use toLowerCase()
// on this.state.search as well
return item.name.toLowerCase().indexOf(this.state.search.toLowerCase())!==-1;
});
return(<div>
<h1> Contact List </h1>
<input type="text"
value={this.state.search}
placeholder="Search Here"
onChange={this.updateSearch.bind(this)} />
<ul>
{
filteredContacts.map((obj, i) =>
<Tab value={obj.name} key={i} />)
}
</ul>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Resources