Simple way to conditionally render styled-components and SVG icons - reactjs

I had a really hard time trying to find a way for a simple action to render user avatar or SVG icon if avatar doesn't exist.
I didn't found any good examples (or maybe I'm just bad at finding simple solutions) and after many days of struggling came up with a solution, but i don't know if what I'm doing is right and will not get me in trouble in future, because I want to do other components with the same pattern and not rework after.
So I'm asking this community if this way for conditional render styled-components and SVG icons is right?
My file where I have all the icons:
Icon.js
import React from "react"
export class Vk extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<svg {...this.props} height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>
)
}
}
export class Ghost extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<svg {...this.props} height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
Sorry, your browser does not support inline SVG.
</svg>
)
}
}
With {...this.props} i can change how my SVG looks like dinamicly from component where i render them.
Now my file where i want to render avatar
Profile.js
import React from "react"
import styled from 'styled-components'
// Here i import my avatar component
import Avatar from "src/shared/Avatar"
export default class Profile extends React.Component {
constructor(props) {
super(props)
this.state = { showProfileMenu: false}
}
// Some function
openCloseProfileMenu = () => {
this.setState({ showProfileMenu: !this.state.showProfileMenu })
}
render() {
return (
<ImportAvatar onClick={this.openCloseProfileMenu} profile={this.props.profile} size='50px' />
)
}
}
// Here i can style me avatar commponent
const ImportAvatar = styled(Avatar)`
display: flex;
cursor: pointer;
`
And my Avatar component Avatar.js
import React from "react"
//Here i import ONE svg icon
import { Ghost } from "src/shared/Icon"
import styled from 'styled-components'
export default class Avatar extends React.Component {
constructor(props) {
super(props)
}
render() {
// Here i destroy passed to component props, get and use what i need and pass to component what left with ...other
const {
profile,
size,
...other
} = this.props;
// I will render svg or img, but they have common styles, so i place them in one place
const commonStyle =
`border-radius: 50%;
height: ${size};
width: ${size};
box-shadow: 0 0 5px 2px ${profile.color};`
// Here i use common and some specific for img styles
const AvatarImg = styled.img`
${commonStyle}
height: 100%;`
// I can render SVG component like this styled(props => <Ghost {...props} />)
// Here i use common and some specific for svg styles
const AvatarSvg = styled(props => <Ghost {...props} />)`
${commonStyle}
fill: grey;`
// and finally I conditionally render component with passed attr, functions, styles and my own if i want
return (
profile.avatar
? <AvatarImg {...other} src={profile.avatar} alt={profile.nickname} />
: <AvatarSvg {...other} />
)
}
}

Related

Display element based on event fired and props passed in

I am trying, to manipulate another element, by, passing props directly to it, and then have it display itself. If I pass true/false.
Live running code:
https://codesandbox.io/s/keen-dan-rt0kj
I don't know if it's possible to have a system of objects, and based on an event, tell a parent to display a child.
App.js
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
state = {
display: false
};
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}
./components/Content.js:
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
display: props.display
};
}
render() {
const { display } = this.state;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Goal:
I want to based on a state, and based on fired event, display an element that already in store of root.
EDIT: I am aware that, this exists and can be used: import PropTypes from 'prop-types', however, I am not sure this is good practice, since it requires some parent or some other component to implement the props.
JUST Tried:
App:
<Content display={this.state.display} content={"Hello World"} />
Content:
<h3>{this.state.content}</h3>
It seems the passed in text, stored in Content state = {content: props.content} does get displayed, wheres, the boolean value does not work directly. Is there something wrong with sending in a bool ?
try this in your Content Component
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
};
}
render() {
return (
<>
{this.props.display?(
<div
id="mydiv"
className="mydiv"
>
<h3>A simple div</h3>
</div>
):null}
</>
);
}
}
The reason this may not be working is because you are initiating the state in a way that does not connect the display props after the component is initialized. This means that after the Content component is "constructed", the state of the Content and it's parent are not linked. This is because the constructor() function is only run once to initialize the state.
The best option you have is to not use the internal state of the Content component. Rather than initializing state with the display prop, just use the display prop in your render function.
Trying something like this might work
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super(props);
}
render() {
const { display } = this.props;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Also I would reccommend using state in the root:
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
constructor(props) {
super();
state = {
display: false
};
}
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}

React Progressbar not Visible?

I have created a React progress bar with the help of third part library
import React from "react";
import { css } from "#emotion/core";
import ClipLoader from "react-spinners/ClipLoader";
// Can be a string as well. Need to ensure each key-value pair ends with ;
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
class AwesomeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true
};
}
render() {
return (
<div className="sweet-loading">
<ClipLoader
css={override}
size={150}
color={"#123abc"}
loading={this.state.loading}
/>
</div>
);
}
}
export default AwesomeComponent;
and then import in index.js file
import AwesomeComponent from './awesomeComponent.js';
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
<AwesomeComponent />
</BrowserRouter>
</Provider>,
document.getElementById("root")
);
In my application i want to show this progresbar when data loading from nodejs api or any button clicked ..But nothing visible in GUI related to progressbar.
If you are using class based component for react-promise-tracker then you need to use HOC promiseTrackerHoc :
import React from "react";
import { css } from "#emotion/core";
import ClipLoader from "react-spinners/ClipLoader";
import { promiseTrackerHoc } from "react-promise-tracker"; // <--------- HERE
// Can be a string as well. Need to ensure each key-value pair ends with ;
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
class AwesomeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true
};
}
render() {
return (
this.props.promiseInProgress && // <--------- HERE
<div className="sweet-loading">
<ClipLoader
css={override}
size={150}
color={"#123abc"}
loading={this.state.loading}
/>
</div>
);
}
}
export default promiseTrackerHoc(AwesomeComponent); // <--------- HERE
WORKING DEMO :
Note :
You can checkout DOC, as they have explained everything, I
think you can help you to find out whatever the scenario you want to
implement
In this example, you can see how you can change the visibility of your spinner just changed the local state by clicking on button
https://codesandbox.io/s/modern-monad-4tipc?file=/src/AwesomeComponent.js

In React JS, how do I tell a parent component that something has happened in the child?

I have a React JS app with a simple hierarchy: ContainingBox wraps two InfoBox components. in this example, I simply want to tell the ContainingBox component 1) that something has been clicked, and 2) which InfoBox (by label name) has been clicked?
Here is some basic code that works in my browser to get this question up & running. All it does it console.log when you click onto one of the InfoBox elements on the page.
Essentially, what I am trying to achieve is that I want the ContainingBox to change state (specifically, border color as rendered) when one of the child InfoBox elements is clicked.
I'm not sure what the right direction here is.
I built this app with React 16.10.2, but I would be happy to read answers pointing me towards the latest 'React way' of thinking.
import React from 'react';
import styled from 'styled-components'
import './App.css';
const StyledInfoBox = styled.div`
width: 100px;
border: solid 1px green;
padding: 10px;
cursor: pointer;
`
class InfoBox extends React.Component {
constructor({blurb}) {
super()
this.state = {
label: (blurb ? blurb.label : ""),
}
this.selectBox = this.selectBox.bind(this);
}
selectBox(e) {
e.preventDefault()
console.log("selectBox")
// how do I tell the ContainingBox component 1) that something has been clicked,
// and 2) which InfoBox (by label name) has been clicked?
}
render() {
const {label} = this.state
return (
<StyledInfoBox onClick={this.selectBox} >
{label}
</StyledInfoBox>
)
}
}
class ContainingBox extends React.Component {
render() {
return (
<div>
<InfoBox key={1} blurb={{label: "Aenean malesuada lorem"}} />
<InfoBox key={2} blurb={{label: "Lorem Ipsum dor ameet"}} />
</div>
)
}
}
function App() {
return (
<div className="App">
<ContainingBox />
</div>
)
}
export default App;
You pass a callback from the parent component to child component via the props.
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
changeNameTo = (newName) => this.setState({name: newName})
render() {
return (
<div>
<h1>{this.state.name}</h1>
<p>
<Child callbackExample={this.changeNameTo} />
</p>
</div>
);
}
}
Then you have your Child component.
class Child extends Component {
render() {
return(
<div>
<button onClick={() => this.props.callbackExample("Doggos")}>
Click me
</button>
</div>)
}
}
When you click the button, the callback is invoked setting the state of the parent, which is then reflected when the parent re-renders.

Managing state when changing backgroundImage

In state I'm setting background image with a photo and I wanted to do 2 buttons, one is changing background image to another photo and another is setting background image back to the first photo.
Here is piece of my code:
\\index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import "semantic-ui/dist/semantic.min.css";
import logo1 from "./modules/images/one.jpg";
ReactDOM.render(
<App bgImage={`url(${logo1})`} />,
document.getElementById("root")
);
\\App.js
import React, { Component } from "react";
import "./App.css";
import NavBar from "./modules/NavBar";
import logo1 from "./modules/images/one.jpg";
import logo2 from "./modules/images/night.jpg";
class App extends Component {
constructor(props) {
super(props);
this.state = {
bgImage: props.bgImage //so here I set the backroundImage with logo1, because I want to logo1 to be on the start of application, but when I change to logo2 I want to have that logo2 even when I refresh page ( but when I do it, constructor of App is setting it to the logo1. So maybe, can I save that logo even when the App is reloading to get the latest logo ?
};
}
ChangeToLightMode = e => {
this.setState({
bgImage: `url(${logo1})`
});
};
ChangeToDarkMode = e => {
this.setState({
bgImage: `url(${logo2})`
});
};
render() {
return (
<div
style={{
display: "flex",
minHeight: "100vh",
flexDirection: "column",
backgroundImage: this.state.bgImage,
height: "100%",
width: "100%"
}}
>
<NavBar
ChangeToDarkMode={this.ChangeToDarkMode}
ChangeToLightMode={this.ChangeToLightMode}
/>
);
}
}
export default App;
\\NavBar.js
import React, { Component } from "react";
class NavBar extends Component {
render() {
return (
<div>
<Menu fixed="top" inverted>
<Menu.Menu position="right">
<Menu.Item onClick={this.props.ChangeToDarkMode}>
DarkMode
</Menu.Item>
<Menu.Item onClick={this.props.ChangeToLightMode}>
LightMode
</Menu.Item>
</Menu.Menu>
</Menu>
</div>
);
}
}
So I implemented a way that I can change background photo but when I click for example logo in my application and the constructor of App is called it is setting my bgImage to the first photo even when I had second photo and I know it. But I want only the first photo to be set in constructor in the start of the application, after it I want to have the photo according to which method I use. So should I store somewhere the bgImage state and do something with it in App constructor ?
If the default value for bgImage needs to be determined before App is rendered, then usually you would do this by passing a prop into the component e.g.
<App bgImage={`url(${logo1})`} />
Then in the constructor, you can set this as the default state
class App extends Component {
constructor(props) {
super(props);
this.state = {
bgImage: props.bgImage
};
}
}

Composed components not working in ReactJS

I have a MySVG react component which has another composed component inside called svgbox. I can't see svgbox rendering. If I use the svg to draw the rectangle (currently in the svgbox) in the MySVG directly, it works.
What change I have to make the composed svgbox to work?
mysvg.js:
import React from 'react'
import svgBox from 'svgbox'
export default class MySVG extends React.Component {
constructor () {
super();
}
render() {
return (
<div className="row">
<div className="col-md-12">
<svg width="700" height="500">
<svgBox/>
</svg>
</div>
</div>
);
}
}
svgbox.js
import React from 'react'
export default class svgBox extends React.Component {
constructor () {
super();
}
render() {
return (
<g transform="translate(50, 20)">
<rect width="100" height="200" styles="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)"/>
</g>
);
}
}
I see 3 posible problems here. You must capitalize the component. Use ./ in import from. Use a space before the closing />.
import React from 'react'
//import svgBox from 'svgbox'
import SvgBox from './svgbox'
export default class MySVG extends React.Component {
constructor () {
super();
}
render() {
return (
<div className="row">
<div className="col-md-12">
<svg width="700" height="500">
//<svgBox/>
<SvgBox />
</svg>
</div>
</div>
);
}
}
Is your component properly exported/imported ?

Resources