So i'm relatively new to React.
I've seen quite a few questions about this but all the solutions have the onClick event attached and executed from within the React component.
I'm wondering, can I interact with my react component the same way I normally would? So my below is a react component
var tableMain = React.createClass({
render: function render() {
console.log(tableModel, "summon the JSON data from tableModel!!");
var thead = React.DOM.thead({},
React.DOM.tr({},
tableModel.map(function (col) {
return React.DOM.th({}, col);
})));
var tbody = tableModel.rows.map(function (row) {
return React.DOM.tr({},
tableModel.cols.map(function (col) {
return React.DOM.td({}, row[col] || "");
}));
});
return React.DOM.table({}, [thead, tbody]);
}
});
Then in my js file, have an onlick event?
Related
I have what I thought would be a simple test to prove state changes, I have another test which does the change by timer and it worked correctly (at least I am assuming so) but this one is trigged by a click event and it's failing my rerender check.
it("should not rerender when setting state to the same value via click", async () => {
const callback = jest.fn();
function MyComponent() {
const [foo, setFoo] = useState("bir");
callback();
return (<div data-testid="test" onClick={() => setFoo("bar")}>{foo}</div>);
}
const { getByTestId } = render(<MyComponent />)
const testElement = getByTestId("test");
expect(testElement.textContent).toEqual("bir");
expect(callback).toBeCalledTimes(1);
act(() => { fireEvent.click(testElement); });
expect(testElement.textContent).toEqual("bar");
expect(callback).toBeCalledTimes(2);
act(() => { fireEvent.click(testElement); });
expect(testElement.textContent).toEqual("bar");
expect(callback).toBeCalledTimes(2); // gets 3 here
})
I tried to do the same using codesandbox https://codesandbox.io/s/rerender-on-first-two-clicks-700c0
What I had discovered looking at the logs is it re-renders on the first two clicks, but my expectation was it on re-renders on the first click as the value is the same.
I also did something similar on React native via a snack and it works correcty. Only one re-render. So it may be something specifically onClick on React-DOM #22940
Implement shouldComponentUpdate to render only when state or
properties change.
Here's an example that uses shouldComponentUpdate, which works
only for this simple use case and demonstration purposes. When this
is used, the component no longer re-renders itself on each click, and
is rendered when first displayed, and after it's been clicked once.
var TimeInChild = React.createClass({
render: function() {
var t = new Date().getTime();
return (
<p>Time in child:{t}</p>
);
}
});
var Main = React.createClass({
onTest: function() {
this.setState({'test':'me'});
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state == null)
return true;
if (this.state.test == nextState.test)
return false;
return true;
},
render: function() {
var currentTime = new Date().getTime();
return (
<div onClick={this.onTest}>
<p>Time in main:{currentTime}</p>
<p>Click me to update time</p>
<TimeInChild/>
</div>
);
}
});
ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
I would like to implement something like this using React hooks:
const header = document.querySelector(".nav-header");
function stickyHeader() {
if (window.pageYOffset > 600) {
header.classList.remove("header");
} else {
header.classList.add("header");
}
}
window.addEventListener("scroll", () => {
stickyHeader();
});
Above, is how I would have manipulated the DOM in vanilla js. I would like to do the same for a component in react.
Possibly try a getClassName method which returns a joined array of classes on every render
For a functional component
const getClassName = () => {
let classes = ["nav"];
if (window.pageYOffset > 600) {
classes.push("header");
}
//more conditions if required
return classes.join(" ");
//returns "nav header" || "nav"
}
and then in your component return method
return (
<div className={getClassName()}><div>
)
className is an element attribute. Therefore you need to set it in your render component function like below:
...
render() {
let className = 'menu';
if (this.props.isActive) {
className += ' menu-active';
}
return <span className={className}>Menu</span>
}
here is an example on how to handle scroll events with React
At first run the program is working correctly.
But after clicking on the sum or minus button the function will not run.
componentDidMount() {
if(CONST.INTRO) {
this.showIntro(); // show popup with next and prev btn
let plus = document.querySelector('.introStep-plus');
let minus = document.querySelector('.introStep-minus');
if (plus || minus) {
plus.addEventListener('click', () => {
let next = document.querySelector(CONST.INTRO[this.state.introStep].element);
if (next) {
next.parentNode.removeChild(next);
}
this.setState({
introStep: this.state.introStep + 1
});
this.showIntro();
});
}
}
As referenced in React documentation: refs and the dom the proper way to reference DOM elements is by using react.creatRef() method, and even with this the documentation suggest to not over use it:
Avoid using refs for anything that can be done declaratively.
I suggest that you manage your .introStep-plus and introStep-minus DOM element in your react components render method, with conditional rendering, and maintain a state to show and hide .introStep-plus DOM element instead of removing it inside native javascript addEventListener click
Here is a suggested modification, it might not work directly since you didn't provide more context and how you implemented the whole component.
constructor() {
...
this.state = {
...
showNext: 1,
...
}
...
this.handlePlusClick = this.handlePlusClick.bind(this);
}
componentDidMount() {
if(CONST.INTRO) {
this.showIntro(); // show popup with next and prev btn
}
}
handlePlusClick(e) {
this.setState({
introStep: this.state.introStep + 1,
showNext: 0,
});
this.showIntro();
});
render() {
...
<div className="introStep-plus" onClick={this.handlePlusClick}></div>
<div className="introStep-minus"></div>
{(this.stat.showNext) ? (<div> NEXT </div>) : <React.fragment />}
...
}
I have a trigger that is supposed to change a state of its child component. The results of both render statements are inconsequential. My only concern here is that I am unsure how to use the trigger trigger to call the leftbehind function without putting leftbehind inside its parent render Another.
My code is below. The goal is to have leftbehind run without having to put it inside the render.
var Another = React.createClass({
leftbehind: function() {
if (this.props.status === "dare") {
alert('Winning!');
}
},
render: function() {
if (this.props.status === "truth") {
return (<p>Yes</p>);
} else {
return (<p>Nope</p>);
}
}
});
var App = React.createClass({
getInitialState:function() {
return {deesfault: "truth"};
},
trigger: function() {
this.setState({deesfault: "dare"});
},
render: function() {
return (
<div>
<p onClick={this.trigger}>{this.state.deesfault}</p>
<Another status={this.state.deesfault}/>
</div>
);
}
});
The reason I do not want to place leftbehind inside the render, is because it is technically supposed to take the place of an API call. And I do not want that to be called inside the render function.
Your implementation executes leftbehind each time <Another> is rendering with its status prop being true. That said, once status is flipped to true, leftbehind will be executed over and over in every following rendering until it is flipped back to false. This will seriously cause problems.
Since the intention is to trigger leftbehind with a click event, I would restructure the components in different ways.
Move leftbehind into the parent component and have it executed along with the click event. If <Another> needs the results, passed them on through props.
var Another = React.createClass({
render() {
return <div>{this.props.params}</div>;
}
});
var App = React.createClass({
getInitialState() {
return {apiRes: null};
},
onClick() {
const res = someAPICall();
this.setState({apiRes: res});
},
render() {
return (
<div>
<p onClick={this.onClick}>Fire</p>
<Another params={this.state.apiRes} />
</div>
);
}
});
Or, move the <p> element into <Another> along with the click event.
var Another = React.createClass({
getInitialState() {
return {apiRes: null};
},
onClick() {
var res = someAPICall();
this.setState({apiRes: res});
},
render() {
return (
<div>
<p onClick={this.onClick}>Fire</p>
<div>{this.state.apiRes}</div>
</div>
);
}
});
var App = function() { return <Another />; }
In the latter, the key logic is handled in the inner component. The outer one is just a container. In the former one, the outer component handles the logic and pass on the results if any. It depends on how the components relate with the API call to decide which suits better. Most importantly, in both cases the API will not execute unless the click event is triggered.
I have a React component that manage multiple accordion in a list, but when i update a children, on React dev tools, it was showing the updated text but on the view/ui, it doesnt update. Please advice.
var AccordionComponent = React.createClass({
getInitialState: function() {
var self = this;
var accordions = this.props.children.map(function(accordion, i) {
return clone(accordion, {
onClick: self.handleClick,
key: i
});
});
return {
accordions: accordions
}
},
handleClick: function(i) {
var accordions = this.state.accordions;
accordions = accordions.map(function(accordion) {
if (!accordion.props.open && accordion.props.index === i) {
accordion.props.open = true;
} else {
accordion.props.open = false;
}
return accordion;
});
this.setState({
accordions: accordions
});
},
componentWillReceiveProps: function(nextProps) {
var accordions = this.state.accordions.map(function(accordion, i) {
var newProp = nextProps.children[i].props;
accordion.props = assign(accordion.props, {
title: newProp.title,
children: newProp.children
});
return accordion;
});
this.setState({
accordions: accordions
});
},
render: function() {
return (
<div>
{this.state.accordions}
</div>
);
}
Edit:
The component did trigger componentWillReceiveProps event, but it still never get updated.
Thanks
After days of trying to solve this, I have came out with a solution. componentWillReceiveProps event was trigger when the component's props changes, so there was no problem on that. The problem was that the childrens of the AccordionListComponent, AccordionComponent was not updating its values/props/state.
Current solution:
componentWillReceiveProps: function(nextProps) {
var accordions = this.state.accordions.map(function(accordion, i) {
// get current accordion key, and props value
// update new props with old
var newAc = clone(accordion, nextProps.children[i].props);
// update current accordion with new props
return React.addons.update(accordion, {
$set: newAc
});
});
this.setState({
accordions: accordions
});
}
I have tried to do only clone and reset the accordions, but apparently it did not update the component.
Thanks
I encountered a similar issue. So it may be relevant to report my solution here.
I had a graph that wasn't getting updated any time I hit just once the load data button (I could see visual changing after the second click).
The graph component was designed as a child of a parent component (connected to the Redux store) and data were passing therefore as a prop.
Issue: data were properly fetched from the store (in parent) but it seems the graph component (child) wasn't reacting to them.
Solution:
componentWillReceiveProps(nextProps) {
this.props = null;
this.props = nextProps;
// call any method here
}
Hope that might contribute in someway.
I usually handle this issue with below technique, its the solution if you are home-alone ;)
var _that = this;
this.setState({
accordions: new Array()
});
setTimeout(function(){
_that.setState({
accordions: accordions
});
},5);