how to change an image url by changing parent-state in ReactJS? - reactjs

I am new to reactJS, but thought I had understood the minimal change and update of DOM. Apparently I was wrong. I have a ReactJS-class Gallery, which renders a list of images (I included two functions of this class). The url of one image (in the images-list) is dependent on the Gallery state (index), so I thought that the image-url would change (forcing a new get request to the server), since it is dependent on the index-value of Gallery, by changing the gallery state.index to something else. How is my thinking incorrect? how can I update an image-url from the gallery class?
getInitialState: function(){
var self = this;
window.addEventListener('keypress', function(e){
if(e.keyCode == 39){
if(self.state.index < self.state.size){
self.setState({index: self.state.index+1});
console.log(self.state.index);
}
}
if(e.keyCode == 37){
if(self.state.index > 0){
self.setState({index: self.state.index-1});
console.log(self.state.index);
}
}
})
render:function(){
var images= [];
var url = "/patient/" + this.state.index.toString();
images.push(<Image src={url} width={500} height={520} left={150} top={this.state.base} />);
return (
<div>
{images}
</div>
);
}
might be a very relevant link (investigating now): Reactjs: how to modify child state or props from parent?

Related

How to retrieve information on a specific button when clicked?

I'm making a film review site where users can search for the title of a film and when entered, a list of films matching the entered title will appear.
I made it so that each film and its corresponding information is contained within a button. I want to add an event listener to the film buttons that will listen to when the button is clicked and retrieve an image link of the movie poster from that specific button that was clicked and plug it into a text field with the ID of "imageSrc". The problem is that currently, it only retrieves the image link of the last film button that is displayed on the page rather than retrieving a specific film button I clicked and the last image link is plugged into the text field "imageSrc" right after entering the search (no film buttons were clicked, it just automatically plugs in the image link associated with the last film result displayed).
function FilmCard({ film }) {
function displaySearchResults({ film }) {
let results = [];
for (let i = 1; i < 5; i++) {
if (film.results[`${i}`]) {
results +=
`<button class='singleFilmCards' onclick=${(document.getElementById(
"imageSrc"
).value = film.results[`${i}`].image)}>` +
"<div class='film-wrapper'>" +
`<img src="${film.results[`${i}`].image}">` +
"<h2>" +
film.results[`${i}`].title +
"</h2>" +
"<h3>" +
film.results[`${i}`].description +
"</h3>" +
"<div>" +
"</button>";
}
}
return results;
}
if (!film) return <></>;
return (
<>
<div
className="FilmList"
dangerouslySetInnerHTML={{
__html: displaySearchResults({ film }),
}}
>
{/* <img alt="Movie Posters" src={film.results[0].image} />
<h2>{film.results[0].title}</h2>
<p>{film.results[0].description}</p> */}
</div>
</>
);
}
This happens because the index variable i in your loop will always have value of 5 in all the onclicks. There is very good reason why setting a pure html in react is done with the dangerouslySetInnerHTML prop - to let the developer know that he/she's doing something that shouldn't be done (there are cases when it's necessary, extremely rare though...)
What you should do, is to make the button into it's own component and handle the events via react event callbacks.
You could try something like this:
function FilmButton({ result, onImageChange }) {
return (
<button
className="singleFilmCards"
onClick={() => onImageChange(result.image)}
>
<div className="film-wrapper">
<img src={result.image} />
<h2>{result.title}</h2>
<h3>{result.description}</h3>
</div>
</button>
);
}
function FilmCard({ film }) {
const handleImageChange = (imageUrl) => {
document.getElementById("imageSrc").value = imageUrl;
};
if (!film) {
return <></>;
}
return (
<div className="FilmList">
{film.results.map((result, index) => (
<FilmButton
key={index}
result={result}
onImageChange={handleImageChange}
/>
))}
</div>
);
}
...in fact, you definitely might want to consider also rewriting the document.getElementById("imageSrc").value = imageUrl in such way, that you won't be manipulating the DOM directly. Because this completely bypasses React.js. There are many ways you rewrite this to use React - from passing the new imageUrl via prop callback into higher component (same as the onImageChange in my example), to using a React context (context would probably be an overkill though :)

ReactJS parent/child list items not rendering properly after an item is removed

Example: https://jsfiddle.net/wbellman/ghuw2ers/6/
In an application I am working on, I have a parent container (List, in my example) that contains a list of children (Hero, in my example). The list is governed by an outside object. For simplicity I declared the object directly in the JS. (In my real application the data store is properly namespaced and so forth.)
The problem I have is in the list I have three elements, if I remove an item from the middle, the rendered list appears to remove the last element. However the outside object reflects the proper list.
For example:
My list has the elements: cap, thor, hulk
If you remove "thor", "cap" and "thor" are rendered
The heroList reflects "cap" and "hulk" as it should
I am relatively new to ReactJs, so there is a good chance my premise is fundamentally flawed.
Note: The example reflects a much more complex application. It's structured similarly for purposes of demonstration. I am aware you could make a single component, but it would not be practical in the actual app.
Any help would be appreciated.
Here is the code from JSFiddle:
var heroList = [
{ name: "cap" },
{ name: "thor"},
{ name: "hulk"}
];
var List = React.createClass({
getInitialState() {
console.log("heros", heroList);
return {
heros: heroList
};
},
onChange(e){
this.setState({heros: heroList});
},
removeHero(i,heros){
var hero = heros[i];
console.log("removing hero...", hero);
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
this.setState({heros:heroList});
},
render() {
var heros = this.state.heros;
var createHero = (hero,index) => {
return <Hero hero={hero} key={index} onRemove={this.removeHero.bind(this,index,heros)}/>;
};
console.log("list", heros);
return (
<ul>
{heros.map(createHero)}
</ul>
)
}
});
var Hero = React.createClass({
getInitialState() {
return {
hero: this.props.hero
}
},
render() {
var hero = this.state.hero;
return (
<li>Hello {hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
ReactDOM.render(
<List />,
document.getElementById('container')
);
Additional: I was having problems copying the code from JSFiddle, anything I broke by accident should work in the JSFiddle listed at the top of this question.
Edit:
Based on the commentary from madox2, nicole, nuway and Damien Leroux, here's what I ended up doing:
https://jsfiddle.net/wbellman/ghuw2ers/10/
I wish there was a way to give everyone credit, you were all a big help.
Changing your Hero class to this fixed the issue of displaying the wrong hero name for me:
var Hero = React.createClass({
render() {
return (
<li>Hello {this.props.hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
i.e. I removed the local state from the class and used the prop directly.
Generally speaking, try to use the local store only when you really need it. Try to think of your components as stateless, i.e. they get something through the props and display it, that's it.
Along these lines, you should consider passing the hero list through the props to your List component as well.
if you really have problems with managing your data you should use Flux or Redux.
in this code:
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
i just dont get why you filer the heroList instead of this.state.heros? every time you add or remove a hero, the heroList in your current scope shouldnt be kept in state? the global heroList is just the initial state.
The problem is with the keys used. Since the key is taken from the index, that key has already been used and thus the hero with that key is shown.
change it to key={Math.random() * 100} and it will work

Sequence animations inside CSSTransitionGroup

We are working on a project with some challenging animation requirements.We are trying to do the animations in CSS and not get JS involved in the animation work. For example we have the following component (simplified for clarity):
var mainMenu = React.createClass({
mixins: [PureRenderMixin],
render: function () {
var _this = this;
var currItem;
var items = this.props.itemList.map(function (item) {
if (!item.get('isOpen')) {
currItem = <MainMenuClosed key={item.get('MenuItemId')}
menuTypeId={item.get('MenuTypeId')}
title={item.get('ItemTitle')}
text={item.get('ItemText')}
imageUrl={item.get('ImageId')}
clickHandler={_this.props.closedClickHandler} />;
} else {
currItem = <MainMenuOpen menuTypeId={item.get('MenuTypeId')}
key={item.get('MenuItemId')}
clickHandler={_this.props.openClickHandler}
items={item.get('children')}
/>;
}
return (
currItem
);
})
return (
<ReactCSSTransitionGroup component="div" className="row" transitionName="mainMenuItems">
{items}
</ReactCSSTransitionGroup>
);
}
});
When a menu item is clicked an action is called, which fetches some stuff from the server and sends it on to the stores. The problem is that once the re-render happens the leaving component fades out for example and the entering component fades in at the same time. I would like to let the leaving component finish its animation sequence and then have the entering menu item begin its animation. Is there a builtin way to create such a sequence?

Can a component know its own DOM tree context?

I am attempting to write a <Heading /> component that will abstract away some of the headaches of dealing with headings (h1, h2, etc) in conjunction with accessibility standards.
The goal being that this component would be able to dynamically choose a h(1,2,3) depending on its closest parent heading. However, Looking up the DOM tree like this remind me more of jQUERY than react. I've looked through the docs and SO but haven't seen anything about it, so I'm not sure if this is even possible.
So the question is: Is it possible for a component to know where it is rendered in the DOM tree and then execute some logic with that info? (probably somewhere in componentWillMount).
You could look upward in the DOM in componentDidMount and componentDidUpdate, but that's messy and uncontrolable. There may be a better solution but this is the first thing that comes to mind.
var headerFactories = ['h1', 'h2', 'h3', 'h4'].map(function(tag){
return React.createFactory(tag)
});
var makeHeader = function(level){
var make = function(){
var factory = headerFactories[make.level]
|| headerFactories[headerFactories.length - 1];
return factory.apply(this, arguments);
};
make.sub = function(){
return makeHeader(make.level + 1);
};
make.level = level;
return make;
}
The api may seem a bit strange, but let's say we have a Page component and an Article child which may have another Article child.
var Page = React.createClass({
render: function(){
// make a root level header
var header = makeHeader();
return <div>
{header(null, "My Page")}
<Article headerFactory={header.sub()} data={foo} subArticle={bar} />
</div>
}
});
var Article = React.createClass({
render: function(){
var subArticle = false;
if (this.props.subArticle) {
subArticle = <Article headerFactory={this.props.headerFactory.sub()} />
}
return <div>
{this.props.headerFactory(null, this.props.data.title)}
</div>
}
});
What happens is when we do headerFactory(null, "foo") we get a component of that header level. When we call .sub() we get a version of that headerFactory with the next header level.
var h1 = makeHeader();
var h2 = h1.sub();
var h3 = h2.sub();
var h4 = h3.sub();
This allows components to have header levels based on their parents without breaking out of React.

BackboneJS - same el for many views

I am using same el for more than 1 view like below. I'm not facing any problem till now. Is this good approach or should i do any changes?
<div id="app">
<div id="app-header"></div>
<div id="app-container"></div>
<div id="app-footer">
</div>
App View:
{
el: "#app",
v1: new View1(),
v2: new View2(),
render: function () {
if (cond1) {
this.v1.render();
} else if (cond2) {
this.v2.render();
}}
}
View 1:
{
el: "#app-container",
render: function (){
this.$el.html(template);
}
}
View 2:
{
el: "#app-container",
render: function (){
this.$el.html(template);
}
}
By reading your question, I do not really see what advantages you could possibly have using this approach rather than having the different div elements being the root el for your views 1, 2, 3 and using
this.$el.html(template)
in the render method.
Your approach could work for a small application, but I think it will become really hard to maintain as the application grows.
EDIT
I still do not really get your point, you could only initialize everything only once in both cases.
Here is a working Fiddle.
By the way I am changing the content by listening to the click event but this is to simplify the example. It should be done by the router.
I do use a mixin to handle such situation, I call it stated view. For a view with all other options I will send a parameter called 'state', render will in-turn call renderState first time and there after every time I set a 'state' renderState will update the view state. Here is my mixin code looks like.
var setupStateEvents = function (context) {
var stateConfigs = context.getOption('states');
if (!stateConfigs) {
return;
}
var state;
var statedView;
var cleanUpState = function () {
if (statedView) {
statedView.remove();
}
};
var renderState = function (StateView) {
statedView = util.createView({
View: StateView,
model: context.model,
parentEl: context.$('.state-view'),
parentView:context
});
};
context.setState = function (toState) {
if (typeof toState === 'string') {
if (state === toState) {
return;
}
state = toState;
var StateView = stateConfigs[toState];
if (StateView) {
cleanUpState();
renderState(StateView);
} else {
throw new Error('Invalid State');
}
} else {
throw new Error('state should be a string');
}
};
context.getState = function () {
return state;
};
context.removeReferences(function(){
stateConfigs = null;
state=null;
statedView=null;
context=null;
})
};
full code can be seen here
https://github.com/ravihamsa/baseapp/blob/master/js/base/view.js
hope this helps
Backbone Rule:
When you create an instance of a view, it'll bind all events to el if
it was assigned, else view creates and assigns an empty div as el for that view and bind
all events to that view.
In my case, if i assign #app-container to view 1 and view 2 as el and when i initialize both views like below in App View, all events bind to the same container (i.e #app-container)
this.v1 = new App.View1();
this.v2 = new App.View2();
Will it lead to any memory leaks / Zombies?
No way. No way. Because ultimately you are having only one instance for each view. So this won't cause any memory leaks.
Where does it become problematic?
When your app grows, it is very common to use same id for a tag in both views. For example, you may have button with an id btn-save in both view's template. So when you bind btn-save in both views and when you click button in any one the view, it will trigger both views save method.
See this jsFiddle. This'll explain this case.
Can i use same el for both view?
It is up to you. If you avoid binding events based on same id or class name in both views, you won't have any problem. But you can avoid using same id but it's so complex to avoid same class names in both views.
So for me, it looks #Daniel Perez answer is more promising. So i'm going to use his approach.

Resources