I'm trying to dynamically render font awesome icon in myself written checkbox component. When I'm trying to update state of a with font awesome icon after clicking on it it is not updating. I've tried to move render to separate function and tried to use react-fontawesome but nothing helps. The state is updating but font awesome icons are the same svg code in html.
...
state = {
checked: this.props.checked
}
toggleCheck = () => {
this.setState({ checked: !this.state.checked });
};
render () {
const iconUnchecked = 'far fa-square';
const iconChecked = 'fas fa-check-square';
const iconClass = this.state.checked ? iconChecked : iconUnchecked;
return (
<span onClick={this.toggleCheck}>
<i className={iconClass} />
</span>
);
}
As I understood the font awesome js manipulates DOM and React manipulates virtual DOM. When font awesome js doing its own stuff React can't rerender it after state change. Im still on React 15 and maybe it's not the issue in React 16. I just found a solution for me to put every with font awesome in a div with unique key. This way React see that div must change because key was changed.
Try that one https://jsfiddle.net/n5u2wwjg/30533/
For me seems to work
Check exactly jsfiddle it works, but not a snippet. Snippet is just to satisfy editor.
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: this.props.checked
}
this.toggleCheck = this.toggleCheck.bind(this);
}
toggleCheck() {
this.setState({ checked: !this.state.checked });
}
render() {
const iconUnchecked = 'far fa-square';
const iconChecked = 'fas fa-check-square';
let iconClass = this.state.checked ? iconChecked : iconUnchecked;
return (
<span onClick={this.toggleCheck}>
<i className={iconClass} />
</span>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css" integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" crossorigin="anonymous">
<div id="app"></div>
I too faced the same trouble. As 200Ok mentioned it correctly, svg--inline-fa is not a virtual DOM so it never gets updated. The best way to solve the problem is to wrap the font awesome elements that would contain the decisive classes.
Related
I'm literally fighting in finding a clean solution to the scroll issue in the IOS devices. In my App.js i've simply the background body and a modal with some contents. When the modal is shown i'd like to block the scroll in the background (myBodyContent) and still let the scroll in the modal component. I'm quite new to both javascript and React and this not helping me at all.
The cleanest solution (according to me) i was able to find is the body-scroll-lock package but it seems i'm not able to successfully use it. here is my code:
App.js
class App extends Component {
targetRef = React.createRef();
targetElement = null;
constructor(props) {
super(props);
}
componentDidMount() {
this.targetElement = this.targetRef.current;
disableBodyScroll(this.targetElement);
}
render() {
const myModal = (
<Modal ref={this.targetRef}>
// my long content here
</Modal>);
return (
<React.Fragment>
{myModal}
<Layout>
<myBodyContent>
</Layout>
</React.Fragment>
);
}
}
Modal.js
class Modal extends Component {
shouldComponentUpdate(nextProps, nextState){
return (nextProps.show !== this.props.show)
}
render () {
return (
<div>
<Auxi>
<Backdrop
show = {this.props.show}
clicked = {this.props.modalClosed}
/>
<div className={style.Modal}
style={{
transform: this.props.show ? 'translateY(0)' : 'translateY(-100vh)', // vh is special unit for outside screen
opacity: this.props.show ? '1': '0'
}}>
{this.props.children}
</div>
</Auxi>
</div>
);
}
}
Modal css
.Modal {
position: fixed;
z-index: 500;
background-color: white;
width: 80%;
overflow-y: hidden;
overflow: auto;
padding-right: 15px; /* Avoid width reflow */
border: 1px solid #ccc;
box-shadow: 1px 1px 1px black;
padding: 16px;
top: 5%;
left: 5%;
box-sizing: content-box;
transition: all 0.3s ease-out;
}
#media (min-width: 600px) {
.Modal {
width: 80%;
height: 80%;
left: 10%;
top: 10%
}
}
With the above code, simply everything is locked and i cannot scroll neither the modal nor the myBodyContent.
Can you help me understanding what i'm doing wrong? Or suggest me some other ways to achieve the same result?
Thanks in advance for your help.
You don't have targetElement (it's null) inside App componentDidMount because you try to set ref for React component but not HTML element.
To fix this you need to forward ref inside Modal component like that:
const myModal = (
<Modal forwardedRef={this.targetRef}>
// my long content here
</Modal>
);
and then :
class Modal extends Component {
shouldComponentUpdate(nextProps, nextState){
return (nextProps.show !== this.props.show)
}
render () {
return (
<div ref={this.props.forwardedRef}>
<Auxi>
<Backdrop
show = {this.props.show}
clicked = {this.props.modalClosed}
/>
<div className={style.Modal}
style={{
transform: this.props.show ? 'translateY(0)' : 'translateY(-100vh)', // vh is special unit for outside screen
opacity: this.props.show ? '1': '0'
}}>
{this.props.children}
</div>
</Auxi>
</div>
);
}
}
Thanks Max, i've tried but unfortunately the result is the same. I've also tried to enclose the Modal in a div directly in the App.js and apply the ref directly there without passing it as props...but it's the same. No way to scroll anything.
I have the following React test app:
class MemoTestApp extends React.Component {
constructor(props) {
super(props)
this.state = {
showOverlay: false,
}
}
render() {
return (
<div>
<div>
<MemoComponent str="Hello World" />
</div>
<div>
<input type="button" onClick={() => this.setState({showOverlay: true})} value="Show Overlay"/>
</div>
{this.state.showOverlay && (
<div className="overlay">
<h2>Overlay</h2>
<MemoComponent str="Hello World" />
</div>
)}
</div>
)
}
}
const Component = (props) => {
console.info('render ' + props.str);
return <div>{props.str}</div>;
}
const MemoComponent = React.memo(Component);
ReactDOM.render(<MemoTestApp />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
position: relative;
min-height: 200px;
}
.overlay {
position: absolute;
padding: 20px;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: white;
}
<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="app"></div>
As you can see, there is a memoized functional component which is rendered twice with the same props. The first rendering takes place immediately, the second one after the user presses the button.
However, the component really is rendered twice, as you can see in the console. React.memo prevents the second rendering of the first instance of the component, but the second instance seems to "now know" that this component has already been rendered with the given props.
Is there a way to make Memoization "global", i.e. so that rendered outputs are shared between different instances of the component?
What is the reason that React.memo is not global by default?
Short answer: Components are reusable, this is by design.
They may have their own state, for example a counter. Or they have side effects, e.g. own intervals, custom logic depending on the DOM nodes.
For that reason, they have to be separate "instances" depending where they live on the DOM (parent node, index or key), and are separately rendered. The result is then memoized per component "instance".
I am struggling somewhere in react app with redux, searched a lot but didn't get the desired solution that is why I am posting the question here,
So my problem is that I have to show a loader when the page loads and when the whole HTML loads loader should be hide, below is my code
class LoginScreen extends React.Component {
state = {
email: '',
password: '',
stayLoggedIn: false,
isLoading: true
};
componentDidMount() {
setTimeout(() => {
this.setState({ isLoading: false });
}, 2000);
}
rendering html based on isLoading but currently i have used set-timeout show and hide the loader I want to know is there any solution to make it work without set-timeout, Any help would be appreciable.
If I'm getting it right "when the whole HTML loads" you want to show a loader while your react app is rendered into the browser. for that what you can do is:
1- If you have bootstrapped your project with Create React App
- Go to public folder, there is a file named index.html
- It will have a <div> element into which your entire react application is rendered by the web bundler which will be webpack in this case.
- Inside of this div write a loader like this
<div id="root">
<section class="loader">
<h1 class="heading">INITIALIZING</h1>
<p class="sub-heading">bear with us ...</p>
</section>
</div>
So whats happening here is when you wrote this piece of code
ReactDOM.render(<App />, document.getElementById('root'));
It basically injects your react app into this root document element and as soon as your application is bundled up, the entire html is loaded in the browser and injected into this div it will replace your loader and your login page will be visible
And here's the CSS for the loader animation
<style>
.loader {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background-color: #fff;
color: #000;
overflow-y: hidden;
}
.loader .heading {
font-size: 36px;
text-align: center;
animation: heart-beat 4s infinite ease-in;
z-index: 1;
}
.loader .sub-heading {
font-size: 18px;
text-align: center;
color: #4b4b4b;
z-index: 1;
}
#keyframes heart-beat {
0% {
opacity: 0.1;
}
50% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}
</style>
2- The second option would be to create your react application without CRA but in that case you'll again have an index.html and you can repeat the above steps.
setTimeout is just an asynchronous operation, when you call the setState function, React compare the old state and new state, and trigger a render to your component based on the new state.
When you have a local state to your component, you inject the redux state as a props to your component, this way don't trigger a render when you have local state, you need to use the componentWillReceiveProps method and call a setState method to change your local state by using setState based on new props.
Note: componentWillReceiveProps method work until React v17
import React from "react";
import ReactDOM from "react-dom";
const delay = async ms => await new Promise(resolve => setTimeout(resolve, ms));
class Loader extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
};
}
componentWillReceiveProps(nextProps) {
this.setState({ isLoading: nextProps.isLoading });
}
render() {
return (
<div style={{ border: "1px solid #999", padding: "10px" }}>
Loader component
<div>Loading: {String(this.state.isLoading)}</div>
</div>
);
}
}
class LoginScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
};
}
async asyncOperation() {
// Show loader
this.setState({ isLoading: true });
// Async operation, like fetch, etc...
await delay(1000);
// Hide loader
this.setState({ isLoading: false });
}
render() {
return (
<div>
<Loader isLoading={this.state.isLoading} />
<br />
<button
onClick={this.asyncOperation.bind(this)}
disabled={this.state.isLoading}
>
{this.state.isLoading ? "Waiting..." : "Call async operation"}
</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<LoginScreen />, rootElement);
<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>
I have a component that render some buttons in the page.
To style the buttons I am using .scss so the classes looks like this:
className={styles.tag}
and the corresponding scss is like this:
.tagsContainer {
display: flex;
flex-flow: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
.tag {
display: block;
background-color: #f4f4f4;
i {
margin-left: 50px;
font-size: 1.4em;
}
}
I was lookin to this codePen that does what I want but is using normal css: Adding and removing classes
How can I adapt that code to use it with scss nad more exactly with this syntax: {styles.tag}?
Regards
Americo
THIS IS AN UPDATE:
I tested this code:
<div onClick={(e) => { e.stopPropagation(); this.collectTags(tag); }}>
<p className={`${this.state.addClass == true ? styles.tag : ""}`}
># {tag.title} <i className="ms-Icon ms-Icon--CirclePlus"></i></p>
</div>
look closely to the p-tag. this code is changing the style onClick but the problem is that is changing all the p-tags in the compoenent and not only the one that was clicked.
Is that what you want?
class App extends React.Component {
state = {
addButtonClass: false
};
toggleClass = () => {
this.setState({
addButtonClass: !this.state.addButtonClass
});
};
render() {
const { addButtonClass } = this.state;
return (
<div className="App">
<button
className={`button ${addButtonClass ? " button--red" : ""}`}
onClick={this.toggleClass}
>Click me </button>
</div>
);
}
}
style.scss
.button {
font-size: 24px;
color: white;
background-color: green;
&--red {
background-color: red;
}
}
All the style reference does is return a string className, so you can manipulate it just like you would any other string. Here's a generic example:
// this can be any string, even an empty string to start
let testCircleClass = `${cssModule.infoCircle}`;
if(test > 0) {
coursesCircleClass += ` ${cssModule.hasContent}`;
}
So you'd probably need something like:
An event handler for the click event.
A state to handle if this event has been processed.
To then tie the styles.tag to this state, once activated.
i want to built an multi select checkbox dropdown in react with es6
my requirement is as below specified in image
I tried doing this click here but it is not working.
You can use one parent component that will keep values in its state and toggle list items. Then you can create component for each list item that will keep active property in state that you can toggle on click.
class ListItem extends React.Component {
constructor(props) {
super(props);
this.state = {active: false}
}
render() {
return (
<a
onClick={() => {
this.setState(prevState => {
let newState = !prevState.active;
this.props.handleClick(newState, this.props.value);
return {active: newState}
})
}}
className={!this.state.active ? '' : 'selected'}
href="#">
{this.props.value}</a>
)
}
}
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
showList: false,
value: []
}
this.handleItemClick = this.handleItemClick.bind(this)
}
componentDidMount() {
document.addEventListener('mousedown', (e) => {
if(!this.node.contains(e.target)) {
this.setState({showList: false})
}
})
}
componentWillUnmount() {
document.removeEventListener('mousedown');
}
renderValue() {
let {value} = this.state;
if(!value.length) return "Select..."
else return value.join(', ')
}
toggleList() {
this.setState(prevState => ({showList: !prevState.showList}))
}
handleItemClick(active, val) {
let {value} = this.state;
if(active) value = [...value, val]
else value = value.filter(e => e != val);
this.setState({value})
}
render() {
return (
<div
ref={node => this.node = node}
className="select">
<button onClick={this.toggleList.bind(this)}>
<span className="select_value">
{this.renderValue()}
</span>
</button>
<div
className={"select_list " + (!this.state.showList && 'hide')}>
<ListItem handleClick={this.handleItemClick} value="Lorem" />
<ListItem handleClick={this.handleItemClick} value="Ipsum" />
<ListItem handleClick={this.handleItemClick} value="Dolor" />
</div>
</div>
)
}
}
ReactDOM.render(
<Select />,
document.getElementById('container')
);
button {
background: white;
width: 100%;
padding: 10px 15px;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
cursor: pointer;
text-align: left;
}
.select_list {
width: 100%;
background: white;
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
}
.select_list a {
padding: 10px 15px;
display: flex;
color: black;
text-decoration: none;
position: relative;
align-items: center;
}
.select_list a:before {
width: 15px;
height: 15px;
content: '';
border: 1px solid rgba(0, 0, 0, .1);
border-radius: 5px;
margin-right: 10px;
display: block;
}
.select_list a.selected:before {
background: #0493D1;
content: '✓';
color: white;
font-size: 11px;
text-align: center;
line-height: 15px;
}
.hide {
display: none;
}
<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="container"></div>
Semantic-UI React Approach
After much digging, I found an old conversation between eugenetumachov and Semantic-UI developers(?). One of the users provided incredibly helpful code that answers this question using Semantic-UI's Dropdown component.
This is done by making use of Dropdown's Dropdown.Menu and Dropdown.Item. Then looping through your options via map to create checkboxes. The only downside is that the workaround does not seem to allow scrolling and will require more CSS. Additionally, based on CSS the checkbox items' background color may turn transparent if you double-click on the dropdown, and the dropdown will collapse on mouse hover. You can bypass the transparency issue by using a class or style property for your Dropdown.Menu and Dropdown.Item.
Semantic-UI developer's response to this type of question appears to be a flat "no" or a
Active items are automatically removed from the Dropdown menu. So you cannot show a "checked" state for an item in the menu.
You could create a similar component out of an Input as a trigger for
a Popup containing a Menu or List of Checkboxes.
Are dropdowns with checkboxes possible? #2417
eugenetumachov's workaround