I am working on JSX and I have the following issue
JS
var results ="";
results = value that changes;
HTML
<div className="content" dangerouslySetInnerHTML={{__html: results}}></div>
I have the variable results that changes when a button is pressed. Originally the value is "" and after the event it becomes another string. I want to display the new string every time the button is pressed. Any suggestions?
You should add that variable to the state of your component. Firstly, add
getInitialState() {
return { value : "initialValue" };
}
Or if using ECMA6,
constructor(props) {
super(props);
this.state = { value : "initialValue" };
}
When you want to change it, call
this.setState({ value : "newValue" });
And on the render method, use:
render() {
return <div className="content" dangerouslySetInnerHTML={{__html: this.state.value}} />;
}
Or something like that. Then when your state changes, React will redraw it, calling render again.
BTW, you should avoid dangerouslySetInnerHTML if possible. If you want, you can comment more about what you are doing to see if there are alternatives.
If your value does not contain HTML, for instance, you should use:
render() {
return <div className="content">{this.state.value}</div>;
}
Which is much nicer and safer :)
Related
The problem i have is that React does not update in the situation below.
I added a forceUpdate() when the component should update just to make extra sure.
The code is simple so there is not much to say.
It's as if React does not see that it should update or am i doing something really wrong here?
class Greetings extends React.Component{
constructor(props){
super(props)
this.switchLanguage = this.switchLanguage.bind(this)
this.state = {
languageID: 0,
}
this.arrayContainingRenderValues = [
<span>{this.props.greetingArray[this.state.languageID]}!</span>,
<span>No greetings for you!!</span>
]
}
switchLanguage(){
this.setState((previousState) => ({languageID: (previousState.languageID + 1) % this.props.greetingArray.length}))
this.forceUpdate()
}
componentDidMount(){
this.timerID = setInterval(this.switchLanguage, 500)
}
componentWillUnmount(){
clearInterval(this.timerID)
}
render(){
return this.arrayContainingRenderValues[0]
//The return below works without problem
return <span>{this.props.greetingArray[this.state.languageID]}!</span>
}
}
let content = <Greetings greetingArray={["Good morning","Bonjour","Buenos días","Guten tag","Bom dia","Buongiorno"]}/>
ReactDOM.render(content, document.getElementById('root'))
The state gets updated, you can see that simply by commenting out the first return.
A i got an answer, it is just that the value of the content in this.arrayContainingRenderValues[] was computed and then fixed when first assigned inside the constructor(), to have it recompute the array had to be reassigned in the render().
So in the end i may as well not use the array at all. But i just wanted to test how react works thanks for the help.
So I am using a hash to store the values of dynamically created rows of input values and I lose focus on the input I am modifying after entering only one character. I think the solution to this may be to use refs to refocus on only the last input changed, but I couldn't get it to work, as I wasn't able to figure out how to specify which element was last changed. Advice on how to solve this is appreciated.
The code below dynamically creates input boxes, and looks up their values based on the unitPriceValueHash. Each variant has an id, and id is used as the key to the hash.
I created a codepen to try and recreate the problem, but the issue im facing doesn't show up in code pen. In my actual app I press 1 for example in the input box, then the cursor is not on the input box anymore.
https://codepen.io/ByteSize/pen/oogLpE?editors=1011
The only difference between the codepen and my code appears to be the fact the the inputs are nested inside a table.
CreateItem(variant) {
const unitPriceValueHash = this.props.unitPriceValueHash
return {
variant_title: variant.variant_title,
variant_price: variant.variant_price,
unit_cost: <TextField
type="number"
onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
key={variant.id}
value={unitPriceValueHash[variant.id] || ''}
/>
};
}
Below is the change of state that modifies the hash
handleUnitPriceChange (id, event) {
const unitPriceValueHash = this.state.unitPriceValueHash
unitPriceValueHash[id] = event
console.log(unitPriceValueHash)
this.setState({unitPriceValueHash: unitPriceValueHash});
//this.updateVariantUnitCost(id, event);
}
There's a couple problems with the code you've shared.
Don't use inline functions. Each render, the function is created again which means that when react compares the props, it looks like the function is different (it is a new/different function each time!) and react will re-render.
Don't modify any objects which exist in the state, instead create a new object. If you modify an object that exists in the state, you're essentially saying you don't want renders to be consistent and reproducible.
I've re-posted your original code with the issues highlighted
CreateItem(variant) {
const unitPriceValueHash = this.props.unitPriceValueHash
return {
variant_title: variant.variant_title,
variant_price: variant.variant_price,
unit_cost: <TextField
type="number"
onChange={(event) => this.handleUnitPriceChange(variant.id, event)}
// ^^^^ - inline functions cause react to re-render every time, instead - create a component
key={variant.id}
value={unitPriceValueHash[variant.id] || ''}
/>
};
}
handleUnitPriceChange(id, event) {
const unitPriceValueHash = this.state.unitPriceValueHash
unitPriceValueHash[id] = event
// ^^^^ - please, please - don't do this. You can't mutate the state like this.
// instead, do the following to create a new modified object without modifying the object in the state
const unitPriceValueHash = Object.assign({}, this.state.unitPriceValueHash, { id: event });
this.setState({ unitPriceValueHash: unitPriceValueHash });
}
In regards to the inline-function, generally the recommendation is to create a new component for this which takes the value as a prop. That might look like this:
class UnitCost extends PureComponent {
static propTypes = {
variantId: PropTypes.number,
variantValue: PropTypes.object,
onUnitPriceChange: PropTypes.func,
}
handleUnitPriceChange(e) {
this.props.onUnitPriceChange(this.props.variantId, e)
}
render() {
return (
<TextField
type="number"
onChange={this.handleUnitPriceChange}
value={this.props.variantValue || ''}
/>
);
}
}
CreateItem(variant) {
const unitPriceValueHash = this.props.unitPriceValueHash
return {
variant_title: variant.variant_title,
variant_price: variant.variant_price,
unit_cost: (
<UnitCost
key={variant.id}
variantId={variant.id}
variantValue={unitPriceValueHash[variant.id]}
onUnitPriceChange={this.handleUnitPriceChange}
/>
),
};
}
Regarding your concerns about focus, react generally won't lose your object focus when re-rendering, so don't ever, ever re-focus an object after an update for this reason.
The only time react will lose focus, is if it completely discards the current DOM tree and starts over from scratch. It will do this if it thinks a parent object has been replaced instead of modified. This can happen because of a missing key prop, or a key prop that has changed.
You have not posted enough code for us to investigate this further. If you want more help you should build a minimum reproducible example that we can run and test.
The solution to this problem had me use an intermediate state to store the value of the input field on change, and a submit AJAX request on an onBlur
class TextFieldWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
value: this.props.variantValue[this.props.variantId] || '',
}
this.handleUnitPriceChange = this.handleUnitPriceChange.bind(this)
this.updateValue = this.updateValue.bind(this)
}
updateValue(value){
this.setState({
value: value,
});
}
handleUnitPriceChange() {
this.props.onUnitPriceChange(this.props.variantId, this.state.value);
}
render(){
return (
<TextField
type="number"
id={this.props.variantId}
key={this.props.variantId}
onChange={this.updateValue}
onBlur={this.handleUnitPriceChange}
value={this.state.value}
/>
);
}
}
I want to render an array of html elements in my component. The reason for storing the data/html in an array is because I want to be able to dynamically load a new element depending on a button-click.
This is how I want to display my array:
<div>
{this.state.steps}
</div>
This is how I initiate my component and array:
componentDidMount() {
this.createProcessStep().then(step => {
this.setState({steps: this.state.steps.concat(step)});
});
}
export function createProcessStep() {
this.setState({processStepCounter: this.state.processStepCounter += 1});
return this.addStepToArray().then(d => {
return this.reallyCreateProcessStep()
});
}
addStepToArray = () => {
const step = {
...Some variables...
};
return new Promise(resolve => {
this.setState({
stepsData: this.state.stepsData.concat(step)
}, resolve)
});
};
"stepsData" is another array that holds data (variables) belonging to each step. "steps" on the other hand, should only hold the html.
This is how one step/element looks like:
<div>
...Some Content...
<button label="+" onClick={ () => {
this.createProcessStep().then(step => {
this.setState({
steps: this.state.steps.concat(step)
});
})
}}/>
...other content...
</div>
This button within each step is responsible for loading/adding yet another step to the array, which actually works. My component displays each step properly, however react doesn't properly render changes to the element/step, which is
to say that, whenever e.g. I change a value of an input field, react doesn't render those changes. So I can actually click on the "+"-button that will render the new html element but whenever a change to this element occurs,
react simply ignores the phenotype of said change. Keeping in mind that the changeHandlers for those steps/elements still work. I can change inputfields, radioButtons, checkboxes etc. which will do exactly what it's
supposed to, however the "re-rendering" (or whatever it is) doesn't work.
Any ideas of what I'm doing wrong here? Thanks!
While you could certainly beat your approach into working, I would advise that you take more common react approach.
You make your components to correctly display themselves from the state . ie as many steps are in the state, your component will display. Than make your add button add necessary information (information, not formated html) to the state.
Here is an example how to use component N times:
const MyRepeatedlyOccuringComponent = (n) => (<p key={n}>There goes Camel {n}</p>)
const App = () => {
const camels = [1,22,333,4444,55555]
const caravan = camels.map((n) => MyRepeatedlyOccuringComponent(n))
return(<div>{caravan}</div>
}
I'm working on a React App using Flux - the purpose of which is a standard shopping cart form.
The trouble I'm having is with mapping over some data, and rendering a child component for each iteration which needs local state in order to handle form data before submitting, as I'm getting conflicting props from within different functions.
The following component is the HTML table which contains a list of all products.
/*ProductList*/
export default React.createClass({
getProductForms: function(product, index) {
return (
<ProductForm
product={product}
key={index}
/>
)
},
render: function() {
var productForms;
/*this is set from a parent component, which grabs data from the ProductStore*/
if(this.state.products) {
productForms = this.state.products.map( this.getProductForms );
}
return (
<div className="product-forms-outer">
{productForms}
</div>
);
}
});
However, each child component has a form, and if I understand correctly, the form values should be controlled by local state (?). The Render method always gets the expects props values, but I want to setState from props, so I can both pass initial values (from the store) and maintain control of form values.
However, componentDidMount() props always just returns the last iterated child. I've also tried componentWillReceiveProps() and componentWillMount() to the same effect.
/*ProductForm*/
export default React.createClass({
componentDidMount: function() {
/*this.props: product-three, product-three, product-three*/
},
render: function() {
/* this.props: product-one, product-two, product-three */
<div className="product-form">
<form>
/* correct title */
<h4>{this.props.productTitle}</h4>
/* This needs to be state though */
<input
value={this.state.quantity}
onChange={this.handleQuantityChange}
className="product-quantity"
/>
</form>
</div>
}
});
Let me know if there's any more details that I can provide to make things more clear - I've removed other elements for simplicity's sake.
Thanks in advance!
this is strange. Alternatively, you can set your state in the constructor to empty values for every field e.g
constructor(props) {
super(props);
this.state = {quantity: ''};
}
and then in your render function do smthg like:
render: function() {
let compQuantity = this.state.quantity || this.props.quantity;
/* this.props: product-one, product-two, product-three */
<div className="product-form">
<form>
/* correct title */
<h4>{this.props.productTitle}</h4>
/* This needs to be state though */
<input
value={compQuantity}
onChange={this.handleQuantityChange}
className="product-quantity"
/>
</form>
</div>
}
This way on the first render it'll use whatever is passed in the props and when you change the value, the handleQuantityChange function will set the state to the new value and compQuantity will then take its value from state.
So I've figured out what the issue was. I wasn't using the key within the getProductForms method properly. I was debugging and just output the index in each form - it was always 0, 1, 2, 3... (obviously). So I looked into how those were relevant to the state for each one.
Given that the order of the array I was using (most recent first), the first iteration always had an index and key of 0, even though it was the newest item. So React probably just assumed the '0' item was meant to be the same from the beginning. I know there's a lot of nuances of react that I don't totally understand, but I think this proves that the state of a looped component is directly associated with it's key.
All I had to do was to use a different value as a key - which was a unique ID I have with each product, and not just use the array index. Updated code:
/*ProductList*/
getProductForms: function(product, index) {
return (
<div key={product.unique_id}>
<ProductForm
product={product}
/>
</div>
)
},
I'm wondering how to dynamically request a component based on a variable value. What I'm trying to accomplish here is the following:
import Template1 from './Template1.jsx';
import Template2 from './Template2.jsx';
var ComponentTemplate = (some_condition === true) ? "Template1" : "Template2"
render() {
<ComponentTemplate prop1="prop1 val" />
}
Is this even possible? If so, how?
It is not clear to me why you need to use a string representation of a class rather than just switch the component or use a conditional render:
var Component = some_condition === true ? Template1 : Template2;
// ...
return ( <Component /> );
But assuming this is an oversimplification, the easiest thing to do would be to use a mapping object that translates a string into a component. With ES2015 enhanced object literals, it becomes fairly straightforward:
var Components = {
Template1,
Template2,
};
var Component = condition ? Components['Template1'] : Components['Template2'];
// ...
return ( <Component /> );
If you are just looking to render different component based on the condition you could actually have 2 other render function and inside render() you could check the condition and call corresponding render
render () {
!this.state.isAuthorized? renderLogin(): renderTweets();
}
renderLogin () {
<LoginView/>
}
renderTweet () {
<ListTweets/>
}
Hope this is what you were looking for!
All the dynamic rendering should inside Render function. Because the JSX compile will depend on the Variable name not the Object reference.
If you write <Component />,
it will transfer to React.createElement('Component').
Cannot refer to the dynamic component you want to choose.
depend on the condition when Render is running. Use different React tag you want.
render() {
condition ? <Template1 /> : <Template2 />
}