I am working now on Exchange rates component. I have following code:
<div class="list-block" v-for="(item, index) in cashData.isNoCross" :key="index + '_exchangeRateList'">
<q-item class="list-block__element">
<q-item-section class="list-block__section">
<div v-for="(pic, index) in item.currency" :key="index">
<img :src="() => getImgUrl(pic)" />
</div>
<span class="indent-left">{{ item.currency }}</span>
</q-item-section>
</q-item>
<q-item class="list-block__element">
<q-item-section class="list-block__section">
<span class="title title--blue">{{ item.buyPrice }}</span>
<div v-for="(pic, index) in item.buyStatus" :key="index">
<img :src="() => getImgUrl(pic)" />
</div>
</q-item-section>
</q-item>
</div>
Here I am trying to display different icons correctly. That is, when the currency increases, the arrow is up, while decreasing down. If it has not changed, then just the dot icon. And also I have flag icons that I also need to show.
methods: {
getImgUrl(pic) {
if (pic === "isUp") {
return require("../../statics/icons/currency-icons/arrow-" + pic + ".svg");
}
if (pic === "isDown") {
return require("../../statics/icons/currency-icons/arrow-" + pic + ".svg");
}
if (pic === 'unchanged') {
return require("../../statics/icons/currency-icons/arrow-" + pic + ".svg");
}
if (pic) {
return require("../../statics/icons/currency-icons/" + pic + ".svg");
}
}
enter code here
My problem is that it can not display images.And instead of one icon for each element, it shows me three and five. Please help, I will be grateful for any answer.
Calling functions to render parts of your template is never a good option and the way you're trying to do it with an inline, anonymous function is simply not going to work.
I would instead provide a data property with those arrow images referenced by pic key and use that in your :src attributes, falling back to the default.
For example
data: () => ({
arrows: {
isUp: require("../../statics/icons/currency-icons/arrow-isUp.svg"),
isDown: require("../../statics/icons/currency-icons/arrow-isDown.svg"),
unchanged: require("../../statics/icons/currency-icons/arrow-unchanged.svg")
},
// and any other data properties you already had
})
<img :src="arrows[pic] || require(`../../statics/icons/currency-icons/${pic}.svg`)" />
Related
Hey so I'm working on a project where I need to display different components when users click on a button. Can you see if there's anyway I can reactor the code to be cleaner? I'm using react/next.js for the frontend.
Setting up useState to control which component is viewed. Using Boolean array as input
const [views, setViews] = useState([true, false, false])
Displaying the buttons that users will click to select view
<nav className='flex flex-row justify-end'>
<button
type='button'
className={`mainBtn p-2 mr-2` + (views[0] ? ' active' : '')}
onClick={() => setViews([true, false, false])}
>Create New Order</button>
<button
type='button'
className={`mainBtn p-2 mr-2` + (views[1] ? ' active' : '')}
onClick={() => setViews([false, true, false]) }
>View orders</button>
<button
type='button'
className={`mainBtn p-2 mr-2` + (views[2] ? ' active' : '')}
onClick={() => setViews([false, false, true]) }
>Manage account</button>
<button
type='button'
className={`mainBtn p-2 mr-2`}
onClick={() => signOut() }
>Sign Out</button>
</nav>
Using conditional rendering to display the desired components
{views[0] && <DisplayCreateNewOrder id={session.user.customer.locationID}/>}
{views[1] && <DisplayPendingOrders id={session.user.customer.locationID}/>}
{views[2] && <DisplayAccount customer={session.user.customer}/>}
any feedback is much appreciated. Also, the last two code blocks are wrapped in a div element with some tailwinds classes.
Thanks
You can simplify the state as only one view is visible at a time, there is no need to store three boolean variables. Full example - Codesandbox.
Store views in an enum/constant -
const Views = Object.freeze({
Create: "Create",
View: "View",
Manage: "Manage"
});
State can simply be current active view, very simple -
const [view, setView] = useState(Views.View);
Buttons can be refactored into one reusable component -
const LinkButton = ({ text, isActive, onClick }) => {
return (
<button
type="button"
className={`mainBtn p-2 mr-2` + (isActive ? " active" : "")}
onClick={onClick}
>
{text}
</button>
);
};
Then used as
<LinkButton
text="View Orders"
isActive={view === Views.View}
onClick={() => setView(Views.View)}
/>
First, from your code it is evident that only one view can appear at any given time. This means that instead of holding 3 booleans, you can just hold the id/name of the view that is currently active:
const {CreateNewOrder, ViewOrders, ManageAccount} = {'create', 'view', 'account'};
const [activeView, setActiveView] = useState(CreateNewOrder);
Then you can use a function to make the conditional more readable:
const isViewActive = view => activeView === view;
And you would use it like this:
{isViewActive(ManageAccount) && <DisplayAccount id={session.user.customer}/>}
There's probably more consolidation you could do by having all three views accept customer instead of some accepting only the ID.
Is there a possibility to set singe value on React rc-slider Range component? At present to do that I need to drag the second handle in front of the first one or the first one drag after the second one and this behavior is allowing user to drag the handle behind the rail if we are on the edge values
I would like to set this up like on the third image it would show from eg. 39 to 39. Here is my code
import React, { useState } from "react";
import Slider, { Range } from "rc-slider";
import "rc-slider/assets/index.css";
export const RangeSlider = ({
min,
max,
error,
displayUnit,
stereo,
onChange
}) => {
const [minVal, setMinVal] = useState(min);
const [maxVal, setMaxVal] = useState(max);
const props = {
onChange: value => {
if (stereo) {
setMinVal(value[0]);
setMaxVal(value[1]);
} else {
setMinVal(value);
setMaxVal(value);
}
onChange(value);
},
min: min,
max: max,
defaultValue: stereo ? [0, 100] : 0
};
return (
<>
{error && (
<span className="position-outside">
<i className="fas fa-exclamation-circle fa text-danger mt-2"></i>
</span>
)}
<div className="text-primary h6 mb-3">
<strong>
{stereo &&
`Od ${minVal}${
displayUnit ? " " + displayUnit : ""
} do ${maxVal}${displayUnit ? " " + displayUnit : ""}`}
{!stereo &&
`${minVal}${displayUnit ? " " + displayUnit : ""}`}
</strong>
</div>
{stereo ? <Range {...props} /> : <Slider {...props} />}
</>
);
};
I am aware that single range slider is for single values, although the client has requested this feature.
Thanks
Firstly my apologies for making a confusion with the title and a question, the title should be different, I have found the bug I thought it was rc-slider, however it was me translating the second rail to be contained within the track, I have explained everything in detail in this post styling rc-slider track to contain handles within itself therefore I am closing this one. Thanks
I have seen similar questions here, but these haven't been helpful so far.
I have a component that has an array state:
eventData: []
Some logic watches for events and pushes the objects to the state array:
eventData.unshift(result.args);
this.setState({ eventData });;
unshift() here is used to push the new elements to the top of the array.
What I want to achieve is rendering the content of the state array. I have written a conditional that checks for a certain state, and based on that state decides what to output.
let allRecords;
if (this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
} else if (!this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
if (this.state.account === this.state.eventData[i].paramOne) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
}
}
Problems that I have with this piece of code:
The code always renders the very last value of eventData state object.
I would like to limit the rendered elements to always show not more than 20 objects (the last 20 records of the state array).
paramTwo is a bool, and according to its value I expect to see either Win or Loose, but the field is empty (I get the bool value via the console.log, so I know the value is there)
Is this even the most effective way of achieving the needed? I was also thinking of mapping through the elements, but decided to stick with a for loop instead.
I would appreciate your help with this.
A few things :
First, as the comments above already pointed out, changing state without using setState goes against the way React works, the simplest solution to fix this would be to do the following :
this.setState(prevState => ({
eventData: [...prevState.eventData, result.args]
}));
The problem with your code here. Is that the arrow function was never called :
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}
}
This function can be reduced to the following (after applying the deconstructing seen in the below code) :
<span>{paramTwo ? 'Win' : 'Lose'}</span>
Next up, removing repetitions in your function by mapping it. By setting conditions at the right place and using ternaries, you can reduce your code to the following and directly include it the the JSX part of your render function :
render(){
return(
<div> //Could also be a fragment or anything
{this.state.allRecords || this.state.account === this.state.eventData[i].paramOne &&
this.state.eventData.map(({ paramOne, paramTwo, paramThree, paramFour, paramFive, paramSix }, i) =>
<div className="event-result-table-container" key={i}> //Don't forget the key like I just did before editing
<div className="result-cell">
{paramOne}
</div>
<div className="result-cell">
<span>{paramTwo ? 'Win' : 'Lose'}</span>
</div>
<div className="result-cell">
{paramThree.c[0]}
</div>
<div className="result-cell">
{paramFour.c[0]}
</div>
<div className="result-cell">
{paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{paramSix.c[0]}
</div>
</div>
)
}
</div>
)
}
Finally, to only get the 20 first elements of your array, use slice :
this.state.eventData.slice(0, 20).map(/* CODE ABOVE */)
EDIT :
Sorry, I made a mistake when understanding the condition you used in your rendering, here is the fixed version of the beginning of the code :
{this.state.allRecords &&
this.state.eventData.filter(data => this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
Here, we are using filter to only use your array elements respecting a given condition.
EDIT 2 :
I just made another mistake, hopefully the last one. This should ahve the correct logic :
this.state.eventData.filter(data => this.state.allRecords || this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
If this.state.allRecords is defined, it takes everything, and if not, it checks your condition.
I cleaned up and refactored your code a bit. I wrote a common function for the repetitive logic and passing the looped object to the common function to render it.
Use Map instead of forloops. You really need to check this this.state.account === this.state.eventObj.paramOne statement. This could be the reason why you see only one item on screen.
Please share some dummy data and the logic behind unshift part(never do it directly on state object), we'll fix it.
getRecord = (eventObj) => (
<React.Fragment>
<div className="result-cell">
{eventObj.paramOne}
</div>
<div className="result-cell">
{eventObj.paramTwo ? <span>Win</span> : <span>Loose</span>}
</div>
<div className="result-cell">
{eventObj.paramThree.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFour.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{eventObj.paramSix.c[0]}
</div>
</React.Fragment>
)
render() {
let allRecords;
if (this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => this.getRecord(eventObj)}</div>;
} else if (!this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => {
if (this.state.account === this.state.eventObj.paramOne) {
return this.getRecord(eventObj);
}
return null;
})}</div>;
}
return (<div className="event-result-table-container">{allRecords}</div>);
}
I'm attempting to create a component that consists of rows of data, which when clicked, open a modal with information relating to that table row. For example, when a user clicks on "team 1", a modal would appear showing a new table displaying each of the users assigned to that team.
I've managed to achieve this using manually provided parameters, however I have no idea how to make the modal dynamically display data depending on which table row has been clicked. Here is a link to a jsfiddle that i've made to show my problem.
getInitialState: function () {
return {
teams:[
{
id: '1',
teamName: 'team 1',
users: ['dave', 'steve', 'jim', 'barry', 'tom', 'harry']
},
]
};
render: function () {
var self = this;
var projectsTable = this.state.teams.map(function (obj, index) {
return (
<tr className="table-teamProject" key={index} data-toggle="modal" data-target="#projectUsersModal" data-id='3'>
<div className="mCellsContainer">
<div className="mCellsNames">{obj.teamName}</div>
<div className="mCellsCount">{obj.users.length} Users</div>
</div>
</tr>
);
});
var projectUsersModal = this.state.teams.map(function (obj, index) {
return (
<div className="modal projectUsersModal fade" id="projectUsersModal" tabIndex={-1} role="dialog" aria-labelledby="myModalLabel">
<div className="modal-dialog" role="document">
<div className="modal-content">
</div>
</div>
</div>
);
});
return (
<div>
<div className="projectsColContainer">
<div className="panel panel-default">
<div className="panel-heading">Projects</div>
<table className="scroll-table">
{projectsTable}
{projectUsersModal}
</table>
</div>
</div>
</div>
);
}
The render() method is creating, what I think would be, a hidden modal for every team you have in your teams array, regardless of if the user requested the modal to show up (clicked on the team's link) or not. A better approach would be to create the specific modal on demand, that's when the user clicks on the team's link.
This can be done by creating a click handler and inside that function you would modify the state by setting the id of the team the modal is about, like so:
onClickTeam: function(teamId) {
this.setState({
openModalTeamId: this.state.openModalTeamId == teamId ? null : teamId
});
}
Then in your render() method you will want to check if this openModalTeamId state property has some value in it, if so and since your are storing the team's id in there, you would want to look for this particular team in your state teams array using the Array.prototype.find and then use the returned result to construct your modal's content.
render: function() {
...
var modalBody;
if (this.state.openModalTeamId) {
var team = this.state.teams.find(function(el) {
return el.id == self.state.openModalTeamId
});
modalBody =
...
<div className="modal-body">
Lets assume this is your modal containing the
following info about the selected team:
<br /><br />
{JSON.stringify(team)}
<br /><br />
<div onClick={(this.onClickTeam.bind(this, team.id))}>
Click me to close
</div>
</div>
...
}
...
}
Once you have that you can just append this new modalBody variable to your render's JSX just like you do in your code using the projectUsersModal variable. If no team was clicked on, then this variable would be undefined and no modal will show up.
return (
<div>
<div className="projectsColContainer">
<table className="scroll-table">
{projectsTable}
{modalBody}
</table>
</div>
</div>
);
jsFiddle
You can use https://github.com/fckt/react-layer-stack .
It allows you to both use variables from closure (which will propagate automatically if you'll provide it to "use" property of Layer) and also set event data from your toggle to modal window. Also you can have "stack" of layers with zIndex, one on another.
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...
I have the following code.
var ImageList = React.createClass({
getComponent: function(index){
console.log(index);
},
render: function() {
var results = this.props.data;
return (
<div className="row">
{results.map(function(result) {
return(
<a className="th medium-3 columns" href="#" onClick= {this.getComponent.bind(this, 1)}>
<img alt="Embedded Image" key={result.id} src={"data:" + result.type + ";" + "base64," + result.image} />
</a>
)
})}
</div>
);
}
});
The second return function basically loops an array of images and shows them. I wanted an OnClick event when clicked should trigger the getComponent method. However if the OnClick event is within the array loop it throws the following error:
Uncaught TypeError: Cannot read property 'getComponent' of undefined.
However if i use the same code and just add the onClick even after the looping function like below:
var ImageList = React.createClass({
getComponent: function(index){
console.log(index);
},
render: function() {
var results = this.props.data;
return (
<div className="row">
{results.map(function(result) {
return(
<img alt="Embedded Image" key={result.id} src={"data:" + result.type + ";" + "base64," + result.image} />
)
})}
<a className="th medium-3 columns" href="#" onClick= {this.getComponent.bind(this, 1)}>
</div>
);
}
});
Ends up working fine. But since i need to keep a unique id for each image only then can i complete the remaining function of getComponent the second method isn't much use for me. Hence is there any way to make it work within the Loop?
Your scope changes within the .map method:
{results.map(function(result) {
// `this` is different inside this anonymous function
})}
What you want to do is either use ES6' fat arrow syntax, which automatically creates an anonymous function with the same scope, or store the current scope of this in a variable:
ES6 Fat Arrow (read more here):
render: function() {
var results = this.props.data;
return (
<div className="row">
{results.map( (result) => {
return(
<a className="th medium-3 columns" href="#" onClick={that.getComponent.bind(that, 1)}>
<img alt="Embedded Image" key={result.id} src={"data:" + result.type + ";" + "base64," + result.image} />
</a>
)
})}
</div>
);
}
});
Note that you'll need a transpiler — such as babel.io — to change this into ES2015 which browsers currently understand to run. This is considered "best practice", as ES6/7 brings better functionality to JS.
Storing a reference to this:
render: function() {
var results = this.props.data,
that = this;
return (
<div className="row">
{results.map(function(result) {
return(
<a className="th medium-3 columns" href="#" onClick={that.getComponent.bind(that, 1)}>
<img alt="Embedded Image" key={result.id} src={"data:" + result.type + ";" + "base64," + result.image} />
</a>
)
})}
</div>
);
}
});
You can use ES6 arrow functions to automatically preserve this context:
results((result) => {
...
})
or just pass this as second param to the map:
results(function(result) {
...
}, this)