How to Switch component in React like an app - javascript

I am a beginner in React, and I would like to make a web app with a dashboard like Windows tiles.
I generate them easily. But I want when I click in one of them, that hide the Dash menu and Display the sub-app "Prog1". And of course, reverse it. When I click the close button, it closes the sub-app and returns to the Dash menu (implying that it hides the Prog1 to display Dash).
I can hide the Dash menu with a function like this:
fctHide = () => {
this.setState({ isActive: false });
}
But how can I display the other subApp with maybe a function like fctShow?
Are we forced to put the function inside a class or just make a general display/hide function?
Is there an easier and newer way to do this (with hooks for example).
My app is on Codepen here : codepen
I can show you here on demand.
I'm surprised to see lots of questions or tutorials to show and hide elements from a button but not to switch between class/HTML/template etc. (like React Native router I guess).
Thanks!

Unless you are converting an old app to React, i.e. not writing a pure React app from the ground up, don't use ReactDOM.render multiple times. As you want to share the active state between components, it should live in their closest common ancestor.
I'm not sure how your dashboard should work, but here's a demo. Here, APP is such closest ancestor. You don't need react-router if you are not using URL routes or the History API.
import React from "react";
import "./styles.css";
class Dash extends React.Component {
render() {
const { isActive, fctHide, fctShow } = this.props;
const elements = ["1", "2", "3", "4"];
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<button
key={index}
onClick={() => {
fctShow(index);
}}
>
{value}
</button>
);
}
// if (isActive) {
return (
<div>
<table>
<tbody>
<tr>
<td> {items} </td>
</tr>
</tbody>
</table>
</div>
);
// } else {
// return null;
// }
}
}
class Prog1 extends React.Component {
render() {
const { isActive, selected, fctHide } = this.props;
if (isActive) {
return (
<div className="contProg1">
<button onClick={fctHide}>Close</button>
<h1>Program 1</h1>
<h2>Test1</h2>
<h2>Test2</h2>
<h2>Test3</h2>
Selected: {selected}
<ul>
<li>AAAAA</li>
<li>BBBBB</li>
<li>CCCCC</li>
</ul>
</div>
);
} else {
return null;
}
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { isActive: true, selected: null };
}
fctShow = selected => {
this.setState({ isActive: true, selected });
};
fctHide = () => {
this.setState({ isActive: false });
};
render() {
const { isActive, selected } = this.state;
return (
<>
<Dash
isActive={isActive}
fctHide={this.fctHide}
fctShow={this.fctShow}
/>
<Prog1 isActive={isActive} selected={selected} fctHide={this.fctHide} />
</>
);
}
}

OK ! I finally use as proposed react-router. But I used the Hooks version (with <A>...)
So, the little dashboard is splitted in X parts :
The HTML with only the minimum and root.
<div id="root"></div>
The CSS (nothing to say more)
The sub apps in a specific folder "Apps"
import React from "react";
import { A } from "hookrouter";
class Prog1 extends React.Component {
render() {
return (
<div class="contProg1">
<button class="close">
{" "}
Close
</button>
<h1>Program 1</h1>
<h2>Test1</h2>
<h2>Test2</h2>
<h2>Test3</h2>
<ul>
<li>AAAAA</li>
<li>BBBBB</li>
<li>CCCCC</li>
</ul>
</div>
);
}
}
export default Prog1;
The router page to switch between apps and main Dash.
import React from "react";
import Prog1 from "./Apps/Prog1";
import Prog2 from "./Apps/Prog2";
import Prog3 from "./Apps/Prog3";
import Prog4 from "./Apps/Prog4";
import Dash from "./App";
const routes = {
"/": () => <Dash />,
"/Prog1": () => <Prog1 />,
"/Prog2": () => <Prog2 />,
"/Prog3": () => <Prog3 />,
"/Prog4": () => <Prog4 />
};
export default routes;
The main page, the Dashboard (App.js).
import React from "react";
import { A } from "hookrouter";
const elements = ["1", "2", "3", "4"];
function Dash() {
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<A href={"/Prog" + (index + 1)}>
<button key={index}>{value}</button>
</A>
);
}
return (
<div className="Dash">
<table>
<tr>
<td> {items} </td>
</tr>
</table>
</div>
);
}
export default Dash;
And to finish, the Index page :
import React from "react";
import { render } from "react-dom";
import "./styles.css";
import { useRoutes } from "hookrouter";
import routes from "./router";
import NoPageFound from "./Apps/404";
function App() {
const routeResult = useRoutes(routes);
return <div className="Dash">{routeResult || <NoPageFound />}</div>;
}
render(<App />, document.getElementById("root"));
It works well. I just need to add something like MemoryRouter or something else to hide the URL and prepare for mobile version.
I'm just a little bit scared when I will insert this part in a Django project.
Or, maybe I should separate it ? (You don't need to answer, I will close I think).
Thanks :)

Related

react router navigation image with text link not working properly

I got a simple vertical nav panel which nav sections consist of image to the left and some text to the right. It looks like the following:
The code of the nav section is as follows:
import React from 'react';
import { connect } from 'react-redux';
import data from '../../data/data.json'
export default class TopBarMenuItems extends React.Component {
constructor(props) {
super(props);
}
render() {
const MENUITEMS = data.menuData.map((item) => { //pls note use of loop here
let srcPath = require(`../../image/topBarMenuIcons/${item.src}`);
return (
<div className="menuItem" dataset={item.id}>
<a href={item.url} className="topbarLink">
<div>
<img src={srcPath} />
</div>
{item.name}
</a>
</div>
)
});
return MENUITEMS;
}
};
The problem is that when I click on the text (some link 1) I get redirected to the desired page, but when I click on the image, I dont. So the ideas is to get redirected to the same page when either image or text is clicked. Any ideas how to fix it would be welcome?
Thank you.
You can use react-router-dom library
import React from 'react';
import { connect } from 'react-redux';
import data from '../../data/data.json'
import { Link } from 'react-router-dom'
export default class TopBarMenuItems extends React.Component {
constructor(props) {
super(props);
}
render() {
const MENUITEMS = data.menuData.map((item) => {
let srcPath = require(`../../image/topBarMenuIcons/${item.src}`);
return (
<div className="menuItem" dataset={item.id}>
<Link to={item.url} className="topbarLink">
<div>
<img src={srcPath} />
</div>
{item.name}
</Link>
</div>
)
});
return MENUITEMS;
}
};

Fill input with buttons values in ReactJs

I'm building a small application in ReactJS, it consists of a grid of buttons with letters as values, what I need to do, is to fill an input field with the letters of the buttons clicked, basically like a keyboard.
I've built the grid with the buttons, each button has a letter, but I'm not sure on how I should code the next part; each button should have two stated, either clicked or not, if its clicked, the letter will appear on the input, if clicked again, it should be removed.
These are my components right now:
Square
import React from "react"
class Square extends React.Component {
render() {
return (
<button type="button" className="square">{this.props.letter}</button>
);
}
}
export default Square;
Input Component
import React from 'react';
class Clear extends React.Component {
render() {
return (
<div className="clear-btn">
<button><span>Clear Word</span><span className="cross-icon">X</span></button>
<input className="cust-input" type="text"/>
</div>
);
}
}
export default Clear;
Main App Component
function App() {
return (
<div className="App">
<div className="container">
<div className="letters">
{LettersJSON.board.map( (letter, index) => <Square key={index} letter={letter}/>)}
</div>
<div className="controls">
<Clear />
</div>
</div>
</div>
);
}
export default App;
If anyone can help me on this it would be great, I don't know what would be a good way to get the value of the button and adding it on the input when clicked.
I imagine this would have to be done with events or something like that, quite honestly I'm just starting to learn React and I'm not sure on how I should arrange all the components so they work together.
This is how the app looks as of now:
Consider the following code, also here is the sandbox for you:
https://codesandbox.io/s/6xpzvpno1r
This is our App component. We will populate the buttons here and give each button its letter, passing it through props. We also give each Button component a state-updater function that will update the state of our App component.
import React from 'react'
import ReactDOM from 'react-dom'
import Button from './Button'
import Input from './Input'
class App extends React.Component {
state = {
letters: ['a', 'b', 'c', 'd', 'e'],
value: '',
}
updateValue = letter => {
console.log('ran')
this.setState({
value: this.state.value + letter,
})
}
createButtons = () => {
const letters = this.state.letters
return letters.map(letter => (
<Button letter={letter} updateValue={this.updateValue} />
))
}
render() {
return (
<div>
{this.createButtons()}
<Input value={this.state.value} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
Button component: here we keep call that state-updating function on click and keep track if it has been clicked before.
import React from 'react'
class Button extends React.Component {
state = {
clicked: false,
}
handleOnClick = () => {
if (!this.state.clicked) {
this.props.updateValue(this.props.letter)
this.setState({
clicked: true,
})
}
}
render() {
return (
<button onClick={this.handleOnClick} disabled={this.state.clicked}>
{this.props.letter}
</button>
)
}
}
export default Button
Lastly we have our Input component: which just consumes the value from the parent App component.
import React from 'react'
class Input extends React.Component {
render() {
return <input value={this.props.value} />
}
}
export default Input
Let me know if this is helpful to you. I feel like this essentially provides the principles you need to get your code to work.
Let's break what you want into steps:
Clicking a component should send its letter to the parent component.
That array of letters should be stored in the parent component
The input's value should be the value of that array, but as a string.
1) For the Square component to be clickable, it needs an onClick handler. On click, we'll call a function that's passed into Square from the parent component:
import React from "react"
class Square extends React.Component {
render() {
const { handleClick, letter } = this.props;
return (
<button type="button" className="square" onClick={() => handleClick(letter)}>
{this.props.letter}
</button>
);
}
}
export default Square;
2) Main app controller needs a state property to store the letters that get clicked so we can keep track of them. We also need to pass these letters to the input component.
class App extends Component {
constructor(props) {
super(props);
this.state = {
clickedLetters: [],
};
}
saveClickedLetter(letter) {
const { clickedLetters } = this.state;
const cloneOfClickedLetters = clickedLetters;
cloneOfClickedLetters.push(letter);
this.setState({ clickedLetters: cloneOfClickedLetters });
}
render() {
const { clickedLetters } = this.state;
return (
<div className="App">
<div className="container">
<div className="letters">
{LettersJSON.board.map( (letter, index) => <Square key={index} letter={letter} handleClick={this.saveClickedLetter}/>)}
</div>
<div className="controls">
<Clear clickedLetters={clickedLetters.length > 0 && clickedLetters.join()}/>
</div>
</div>
</div>
)
}
}
export default App;
Finally, let's pass in the clickedLetters prop to input's value attribute:
import React from 'react';
class Clear extends React.Component {
render() {
const { clickedLetters } = this.props;
return (
<div className="clear-btn">
<button><span>Clear Word</span><span className="cross-icon">X</span></button>
<input value={clickedLetters} className="cust-input" type="text"/>
</div>
);
}
}
export default Clear;

Rendering a canvas object received from props

Good day!
I am new to React and html2canvas. I am making an app which will take "screenshots" of my DOM using html2canvas then store it to an array of screenshots which will then be also rendered on the screen.
I am storing each <canvas> object received from the html2canvas promise to an array then pass it to my ScreenshotsContainer component which passes the array to the Screenshots component. The Screenshots component will then map the array of <canvas> objects to individual Screenshot components.
In App.js, I am calling the html2canvas function then pass the array to ScreenshotsContainer component
import React, { Component } from 'react';
import ScreenshotsContainer from './containers/ScreenshotsContainer/ScreenshotsContainer'
import html2canvas from 'html2canvas';
import './App.css';
class App extends Component {
state = {
canvasArray: []
}
getScreenshotHandler = () => {
console.log("[Canvas Array from state length:]" + this.state.canvasArray.length)
let canvasArray = this.state.canvasArray;
html2canvas(document.body).then((canvas) => {
canvasArray.push(canvas)
});
console.log("[Canvas Object value: ]" + canvasArray);
this.setState({ canvasArray: canvasArray })
}
render() {
return (
<React.Fragment>
<button onClick={this.getScreenshotHandler}>Get Screenshot</button>
<ScreenshotsContainer canvasArray={this.state.canvasArray} />
</React.Fragment>
);
}
}
export default App;
The ScreenshotsContainer component will pass the received array to the Screenshots component:
import React, { Component } from 'react';
import './ScreenshotsContainer.css'
import Screenshots from '../../components/Screenshots/Screenshots';
class ScreenshotsContainer extends Component {
render() {
return (
<div className="ScreenshotsContainer">
<Screenshots canvasArray={this.props.canvasArray} />
</div>
);
}
}
export default ScreenshotsContainer;
The Screenshots component will map the array and pass each canvas object to the Screenshot component:
import React, { Component } from 'react';
import Screenshot from './Screenshot/Screenshot';
class Screenshots extends Component {
render() {
const screenshots = this.props.canvasArray.map(canvas => {
return (
<Screenshot
key={Math.random}
canvasObj={canvas}
/>
)
})
return (
<React.Fragment>
{screenshots}
</React.Fragment>
);
}
}
export default Screenshots;
Here is the Screenshot component
import React from 'react';
import './Screenshot.css';
const screenshot = (props) => (
<div className="Screenshot" >
<canvas ref={props.canvasObj} style={{
width: '10%',
height: '10%'
}} />
</div>
);
export default screenshot;
What I actually get when pressing the button:
Actual screenshot of my result
I was wondering which part went wrong. Any help would be appreciated.
This particular library works in a specific way (looks like it's doing a lot of "magic" under the hood - you should look at the source code here more specifically the renderer folder inside src)
Saving the canvas to the state inside of an array (the correct react way of doing things) will be a problem as it saves it as a complex object with many methods etc... and we can not render objects... This lib was not written with React in mind...
The code sample below is a simple implementation in React...
Here is a live demo: https://codesandbox.io/s/9y24vwn1py
import React, { Component } from 'react';
import html2canvas from 'html2canvas';
class App extends Component {
constructor(props) {
super(props);
this.captureRef = React.createRef();
this.displayRef = React.createRef();
}
getScreenshotHandler = () => {
html2canvas(this.captureRef.current).then(canvas =>
this.displayRef.current.appendChild(canvas),
);
};
render() {
return (
<div>
<div ref={this.captureRef}>
<h2>This enitre div will be captured and added to the screen</h2>
</div>
<button onClick={this.getScreenshotHandler}>Get Screenshot!</button>
<section>
<h5>Your screenshots will be availbale below</h5>
<div ref={this.displayRef} />
</section>
</div>
);
}
}
export default App;
EDIT: based on the comment below here is yet another workaround:
class App extends Component {
constructor(props) {
super(props);
this.state = { canvasArray: [] };
this.captureRef = React.createRef();
}
getScreenshotHandler = () => {
html2canvas(this.captureRef.current).then(canvas =>
this.setState({
canvasArray: [canvas.toDataURL(), ...this.state.canvasArray],
}),
);
};
renderCanvas = () => {
return this.state.canvasArray.map((canvas, i) => {
return <img key={i} src={canvas} alt="screenshot" />;
});
};
render() {
return (
<div className="wrapper">
<div ref={this.captureRef}>
<p>This enitre div will be captured</p>
</div>
<button onClick={this.getScreenshotHandler}>Get Screenshot!</button>
<section>
<h5>Your screenshots will be availbale below:</h5>
{this.renderCanvas()}
</section>
</div>
);
}
}
Link to live demo: https://codesandbox.io/s/1r213057vq

Dynamically rendered Tag is always lowercase

I am trying to output some svgs and output them from a list, here is my render method:
render() {
const renderTag = () => {
const Tag = this.props.id
return(<Tag />)
}
return (
<div key={this.props.name} className="social-box">
<a className={this.props.id + "-link"}>
{renderTag()}
</a>
</div>
)
}
However, the DOM node is always lowercase i.e. <facebook> rather than <Facebook> this.props.id is correctly rendered to the console as Facebook. Can anyone tell me why react or the browser incorrectly renders as lowercase, and therefore not the component, and how to fix?
It's a technical implementation of React, all tags get lowercased on this line here, AFAIK it's not possible to render non-lowercased tags and that is by design.
Read more here.
i suggest that you would take a look at this article about dynamic components.
The most relevant example from the article:
import React, { Component } from 'react';
import FooComponent from './foo-component';
import BarComponent from './bar-component';
class MyComponent extends Component {
components = {
foo: FooComponent,
bar: BarComponent
};
render() {
const TagName = this.components[this.props.tag || 'foo'];
return <TagName />
}
}
export default MyComponent;
you most likely have a limited amount of components that could be rendered, so you might create a dictionary that contain a key (name of the component) to the component itself (as shown in the example) and just use it that way:
import Facebook from './FaceBook';
import Twitter from './Twitter';
const components = {
facebook: Facebook,
twitter: Twitter
};
render() {
return <div key={this.props.name} className="social-box">
<a className={this.props.id + "-link"}>
<components[this.props.id] />
</a>
</div>;
}
I find the answer eventually. #TomMendelson almost had the answer, but it needed fleshing out a bit more.
A function to create the component outside of the render method, suggested by #ShubhamKhatri actually did the job. Here's the final code:
import React from 'react';
import Facebook from './svg/Facebook';
import LinkedIn from './svg/LinkedIn';
import Twitter from './svg/Twitter';
import Pinterest from './svg/Pinterest';
class SocialMediaBox extends React.Component {
renderElement(item) {
const Components = {
'Facebook': Facebook,
'Twitter': Twitter,
'Pinterest': Pinterest,
'LinkedIn': LinkedIn
}
return React.createElement(Components[item], item);
}
render() {
const Element = this.renderElement(this.props.id)
return
(
<div>
{Element}
</div>
)
}
}
export default SocialMediaBox;
Thanks for the question and answers; alongside the answers given in Dynamic tag name in jsx and React they helped me to find a solution in my context (making a functional component in Gatsby with gatsby-plugin-react-svg installed):
import React from "react"
import FirstIcon from "../svgs/first-icon.inline.svg"
import SecondIcon from "../svgs/second-icon.inline.svg"
import ThirdIcon from "../svgs/third-icon.inline.svg"
const MyComponent = () => {
const sections = [
{ heading: "First Section", icon: () => <FirstIcon /> },
{ heading: "Second Section", icon: () => <SecondIcon /> },
{ heading: "Third Section", icon: () => <ThirdIcon /> },
]
return (
<>
{sections.map((item, index) => {
const Icon = item.icon
return (
<section key={index}>
<Icon />
<h2>{item.heading}</h2>
</section>
)
})}
</>
)
}
export default MyComponent
As mine is a Gatsby project I used the above mentioned plugin, but it itself process svgs with svg-react-loader so the basic principle should work in any React project using this package.

How to send this.state from a component to relay root container

I want to change my root query parameter based on the this.state.eventid which is a child component, but I have no clue how to get props to relay root container. I started based on relay-starter-kit.
I have React component that has a dropdown menu, and onSelect it setStates for eventId
renderAttend() {
if (this.props.groups != null && this.state.success != true) {
var events = this.props.events.map(function(event){
var boundSelect = () => {this.setState({eventid:event.nodes[0].id})}
return <MenuItem style={{fontSize:20}}eventKey={event.nodes[0].id} onSelect={boundSelect.bind(this)}>{event.nodes[0].properties.summary} / {event.nodes[0].properties.start}</MenuItem>
},this)
var teams = this.props.groups.map(function(team){
var boundSelect = () => {this.setState({teamid:team.nodes[0].id})}
return <MenuItem style={{fontSize:20}}eventKey={team.nodes[0].id} onSelect={boundSelect.bind(this)}>{team.nodes[0].properties.name}</MenuItem>
},this)
return (
<div>
<ButtonGroup>
<DropdownButton style={{padding:"15px",fontSize:20}}title="Events" id="bg-vertical-dropdown-2">
{events}
</DropdownButton>
<DropdownButton style={{padding:"15px",fontSize:20,marginLeft:"5px"}} title="Groups" id="bg-vertical-dropdown-2">
{teams}
</DropdownButton>
</ButtonGroup>
</div>
)
}
}
I want to use this state to somehow change my root query...
my approute...
import Relay from 'react-relay';
export default class extends Relay.Route {
static paramDefinitions = {
eventId: {required: false}
};
static queries = {
Event : () => Relay.QL`query{eventState(eventId:$eventId)}`,
};
static routeName = 'AppHomeRoute';
}
and my app.js
import 'babel-polyfill';
import App from './components/App';
import AppHomeRoute from './routes/AppHomeRoute';
import React from 'react';
import ReactDOM from 'react-dom';
import Relay from 'react-relay';
ReactDOM.render(
<Relay.RootContainer
Component={App}
route= {new AppHomeRoute}
renderLoading={function() {
return <div style= {{display:"flex",justifyContent:"center",marginTop:"55px"}}> <h1>Loading...</h1></div>;
}}
renderFailure={function(error, retry) {
return (
<div>
<h1>Click Refresh</h1>
</div>
);
}}
/>,
document.getElementById('root')
);
Now I want to this.state.eventid from the react component to update my root query, but I have no idea how to pass data from child component to react root.container. I do not want to use react-router for this :)
p.s. this.props.events were passed to me by an ajax call so they are not saved in relay/graphql data.
For such a case, the better thing to do is to wrap your root query into a story like
{
store {
events(eventId:$eventId)
}
}
So in the root query you only have
export default class extends Route {
static queries = {
app:() => Relay.QL`query { store }`
};
static routeName = "AppRoute";
}
And in the page you create a fragemnt like
let RelayApp = createContainer(SomeComponent, {
initialVariables: {
eventId: null
},
fragments: {
app: () => Relay.QL `
fragment on Store {
id
events(eventId: $eventId) {
pageInfo {
hasNextPage
}
edges {
cursor
node {
name
...
}
}
}
}
`,
},
});
export
default RelayApp;
For the child component, you set the eventId and onChange event handler as props from parent component. And in the parent componet you implement the event handler and call this.props.setVariables({eventId: someVal}) like
// Child Component
export default class Menu extends Component {
render() {
return(
<ul>
<li onClick={() => this.props.selectItem(val)}>{val}</li>
...
</ul>
)
}
}
// Parent Component
class Main extends Component {
_selectItem = (val) => {
this.props.relay.setVariables({eventId: val});
}
render() {
return(
<div>
<Menu selectItem={() => this._selectItem}/>
</div>
)
}
}
let RelayApp = ...
export default Main
Hope this will help.
There is no easy way to solve this. Either use react-router-relay or nest your query like this and use this.props.relay.setVariables()
viewer {
eventState(eventid:$eventid) {
data
}
}

Categories

Resources