Add Routing to Breadcrumb component - javascript

I've created a Breadcrumb component but I'm struggling to add routing to it.
So far, the component is able to load a custom number of nodes based on how many elements we send to an array and it logs into the console the name of the node when clicked.
What it is needed is to make this breadcrumb change the url when a node is clicked.
It should contain something like this:
<Switch>
<Route path="/component0" component={Component0} />
<Route path="/component1" component={Component1} />
...
</Switch>
Is it possible to make it for a custom number of nodes?
This is the code so far:
Parent component:
import React, { useState } from 'react';
import Breadcrumbs from './Breadcrumbs';
export interface BreadcrumbProps {}
export function Breadcrumb(props: BreadcrumbProps) {
const [crumbs, setCrumbs] = useState(['Home', 'Category', 'Sub Category']);
const selected = (crumb: any) => {
console.log(crumb);
};
return (
<div>
<Breadcrumbs crumbs={crumbs} selected={selected} />
</div>
);
}
export default Breadcrumb;
Child component:
function Breadcrumbs(props: any) {
function isLast(index: number) {
return index === props.crumbs.length - 1;
}
return (
<nav className=''>
<ol className=''>
{props.crumbs.map((crumb, ci) => {
const disabled = isLast(ci) ? 'disabled' : '';
return (
<li key={ci} className=''>
<button className={`btn btn-link ${disabled}`} onClick={() => props.selected(crumb)}>
{crumb}
</button>
</li>
);
})}
</ol>
</nav>
);
}
export default Breadcrumbs;

Related

passing react hooks into component

I'm new to React. I'm trying to add additional functionality of deleting the record from the list by setting the value.
here is my App.js
import React, { useState } from "react";
import data from "./data";
import List from "./List";
function App() {
const [movies, setMovie] = useState(data);
return (
<main>
<section className='container'>
<h3>{movies.length} Movies to Watch</h3>
<List movies={movies} setMovie />
<button onClick={() => setMovie([])}>clear all</button>
</section>
</main>
);
}
export default App;
In List.js, Im trying to delete the record when clicking on Watched button. Can I call setMovie inside the List component? is it a correct way?
List.js
import React from "react";
const List = ({ movies }, setMovie) => {
return (
<>
{movies.map((movie) => {
const { id, name, year, image } = movie;
return (
<article key={id} className='person'>
<img src={image} alt={name} />
<div>
<h4>{name}</h4>
<button
className='btn'
onClick={(id) =>
setMovie(movies.filter((movie) => movie.id !== id))
}
>
watched
</button>
<p>{year}</p>
</div>
</article>
);
})}
</>
);
};
export default List;
You have two mistakes in your code. First:
<List movies={movies} setMovie />
This shorthand assigns a value of true to setMovie. To assign the setMovie function to it, you must instead do:
<List movies={movies} setMovie={setMovie} />
And secondly this:
const List = ({ movies }, setMovie) => {
Should be this:
const List = ({ movies, setMovie }) => {
try:
<List movies={movies} setMovie={setMovie} />
this way the funcition will appear in the List component as a prop.
The way you were doing, it will just appear as true

DOM render not triggering after updating hooks

I am trying to delete an item (const removeItem) from a list using an onClick event in React.
The state is managed with hooks. I know my way of deleting the item is not the right way yet (i'm putting the name to null), but this is not the issue.
After i set a user to null (i update the users object), i expect a render to happen (useEffect) but it does not. If i switch components and go back to this one, it works, but when clicking the X button, nothing happens in the view.
component Home:
import React, { Suspense, useState, useEffect } from "react";
const Home = props => {
const [users, setUsers] = useState(props.users);
console.log(users);
useEffect(() => {}, [users]);
const addItem = e => {
users.push(e);
console.log(e);
e.preventDefault();
};
const removeItem = item => {
users.forEach(user => {
if (user.id === item) {
user.name = null;
}
});
console.log(users);
setUsers(users);
};
return (
<div className="Home">
<form className="form" id="addUserForm">
<input
type="text"
className="input"
id="addUser"
placeholder="Add user"
/>
<button className="button" onClick={addItem}>
Add Item
</button>
</form>
<ul>
{users.map(item => {
return (
<>
<li key={item.id}>{item.name + " " + item.count}</li>
<button className="button" onClick={() => removeItem(item.id)}>
X
</button>
</>
);
})}
</ul>
</div>
);
};
export default Home;
How i get my users object:
import React from "react";
const LotsOfUsers =[...Array(100).keys()].map((item, key) => item = {
name : `User ${key}`,
id: key,
count: 0
})
export default LotsOfUsers;
main App:
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useRouteMatch,
useParams
} from "react-router-dom";
import Home from "./Home"
import CTX from './store'
import LotsOfUsers from "./LotsOfUsers";
export default function App() {
return (
<CTX.Provider value={{}}>
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/topics">
<Topics />
</Route>
<Route path="/">
<Home users={LotsOfUsers} text="hello world"/>
</Route>
</Switch>
</div>
</Router>
</CTX.Provider>
);
}
function About() {
return <h2>About</h2>;
}
function Topics() {
let match = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
{/* The Topics page has its own <Switch> with more routes
that build on the /topics URL path. You can think of the
2nd <Route> here as an "index" page for all topics, or
the page that is shown when no topic is selected */}
<Switch>
<Route path={`${match.path}/:topicId`}>
<Topic />
</Route>
<Route path={match.path}>
<h3>Please select a topic.</h3>
</Route>
</Switch>
</div>
);
}
function Topic() {
let { topicId } = useParams();
return <h3>Requested topic ID: {topicId}</h3>;
}
Looking into your code, I've noticed 2 times that you change users without the use of setUsers.
const addItem = e => {
users.push(e); // <--- here
console.log(e);
e.preventDefault();
};
const removeItem = item => {
users.forEach(user => {
if (user.id === item) {
user.name = null; // <--- here
}
});
console.log(users);
setUsers(users);
};
In that way, you are updating your users, without letting react know about it. On both cases you have to update users with setUsers and not mutating the array directly.
const addItem = e => {
setUsers(users.concat(e)); // sidenote: if e is your event, then you might be looking for e.target.value here
console.log(e);
e.preventDefault();
};
const removeItem = item => {
setUsers(users.filter(user => user.id !== item));
};
Both .concat() and .filter() use your users array, and return a new array based on the changes you want to apply, and then is used by setUsers to update your users.
So, in extend what I'm actualy doing on removeItem is:
const removeItem = item => {
const newUsers = users.filter(user => user.id !== item); // users remains untouched
setUsers(newUsers);
};
Also, you don't need useEffect hook for this scenario to work properly.
I hope this solves your problem.
You are committing THE Fundamental sin of the react universe. "Mutation"
of state.
const removeItem = item => {
users.forEach(user => {
if (user.id === item) {
user.name = null; // <--- HERE
}
});
console.log(users);
setUsers(users);
};
Check this codesandbox for a working demo of this issue.
https://codesandbox.io/s/jovial-panini-unql4
The react reconciler checks to see whether the 2 objects are equal and since you have just mutated and set the value it registers as the same object and there will be no state change triggered. Hence the view will not be re-rendered and useEffect will not be triggered.

How I can create one component for different JSON files with the same structure

I have one thousand JSON files with the same structure. I need to create one the component that contains react-router to render those JSON Files
I wanted to create different components, but it not a solution for the React.js
JSON:
[{
link: "link",
prodLink: "https://link.com",
stageLink: "https://link.com",
status: "Failed",
titleDifferent: "No Diff",
bodyDiffernet: "No Diff"
},
{
link: "link",
prodLink: "https://link.com",
stageLink: "https://link.com",
status: "Failed",
titleDifferent: "No Diff",
bodyDiffernet: "No Diff"
}]
App.js
const Result1 = () => {
return(
<div>
{
data1.map(({link, status,prodLink,stageLink, titleDifferent, bodyDiffernet})=> <Result
link = {link}
status = {status}
prodLink = {prodLink}
stageLink = {stageLink}
titleDifferent = {titleDifferent}
bodyDiffernet = {bodyDiffernet}
/>)}
</div>
)
}
class App extends Component {
render() {
return (
<div className="App">
<Router>
<div>
Back
</div>
<div>
<Link to={`/links/${data[0].name}`}>
<div>
<span>{data[0].name}</span>
</div>
</Link>
<Route exact path="/links/:name" component = {Result1}/>
</div>
</Router>
</div>
);
}
}
export default App;
Result.js
import React, {Component} from 'react';
import './Result.css';
class Result extends Component{
render(){
const {link, status,prodLink,stageLink, titleDifferent, bodyDiffernet} = this.props;
return(
<div className="container">
<div>
<span className='url'>{link}</span>
<span> - </span>
<span className='status'>{status}</span>
<span className='prod'><a href = {prodLink}>Prod</a></span>
<span className='stage'><a href = {stageLink}>Stage</a></span>
</div>
<div className='diff-title'><span style= {{fontSize: 15}}>Title: </span>{titleDifferent}</div>
<div>
<span style= {{fontSize: 15}}>Body: </span>
<span className='diff-body' dangerouslySetInnerHTML={{ __html: bodyDiffernet}}></span>
</div>
</div>
)
}
}
export default Result;
Expected: that I create an app with 1000 links and when I click on of that link, that should be rendered Result.js component from one of the JSON files.
Actual: I have nothing.
Not sure where your magical data comes from and if it's available in your Result component but if it is then Result can look like this:
class Result extends Component {
render() {
const name = this.props.match.params.name;
const item = data.find(item => item.name === name);
if (!item) {
return 'not found';
}
const {
link,
status,
prodLink,
stageLink,
titleDifferent,
bodyDiffernet,
} = item;
return 'your jsx';
}
}
Then you can create your links like this:
<div className="App">
<Router>
<div>
Back
</div>
<div>
{data.map(item => (
<Link
to={`/links/${item.name}`}
key={item.name}
>
<div>
<span>{item.name}</span>
</div>
</Link>
))}
<Route
exact
path="/links/:name"
component={Result}
/>
</div>
</Router>
</div>

Update state between different Components on ReactJS

I am trying to update the state from an other component to an other component.
I want on header.jsx the state total to be updated when i click on add to cart button on product.jsx
Here is my code
index.jsx
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Header from './header';
import Footer from './footer';
import Posts from './posts';
import Post from './post';
import Products from './products';
import Product from './product';
import Page from './page';
// Load the Sass file
require('./style.scss');
const App = () => (
<div id="page-inner">
<Header />
<main id="content">
<Switch>
<Route exact path={Settings.path + 'products/:product'} component={Product} />
</Switch>
</main>
<Footer />
</div>
);
// Routes
const routes = (
<Router>
<Route path="/" component={App} />
</Router>
);
render(
(routes), document.getElementById('page')
);
header.jsx
import React from "react";
import { Link } from "react-router-dom";
class Header extends React.Component {
constructor(props) {
super(props);
this.state = { products: [], total: 0 }
var total = 0;
this.cartUpdated = this.cartUpdated.bind(this)
}
componentDidMount() {
//let cart = localStorage.getItem('total');
// this.setState({ total: 100 });
}
cartUpdated()
{
this.setState({ total: cart+100 });
}
render() {
return (
<div className="cart-icon p-3 m-auto">
Cart/ Total: <span className=""><span className="cart">€</span>{this.state.total}</span><i className="fas fa-shopping-cart" />
</div>
);
}
}
export default Header;
product.jsx
import React from "react";
import NotFound from "./not-found";
import "react-image-gallery/styles/scss/image-gallery.scss";
import ImageGallery from 'react-image-gallery';
class Product extends React.Component {
constructor(props) {
super(props);
this.state = { product: {}, total: 0
};
// ACTIONS
// addToCart
this.addToCart = this.addToCart.bind(this);
}
addToCart()
{
this.props.cartUpdated;
/*
let total = localStorage.getItem('total')
? JSON.parse(localStorage.getItem('total')) : {};
localStorage.setItem('total', 100); */
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
.......
};
renderProduct() {
if (this.state.product.images) {
const images = [];
if (this.state.product) {
this.state.product.images.map((image, i) => {
var new_image = {"original":image, "thumbnail":image} ;
images.push(new_image);
});
}
return (
<div className="col-md-12">
<div className="row">
<div className="col-md-6">
<ImageGallery items={images} showPlayButton={false} showFullscreenButton={false} thumbnailPosition="left" />
</div>
<div className="col-md-6">
<h4 className="card-title">{this.state.product.name}</h4>
<p className="card-text">
<strike>${this.state.product.regular_price}</strike>{" "}
<u>${this.state.product.sale_price}</u>
</p>
<p className="card-text">
<small className="text-muted">
{this.state.product.stock_quantity} in stock
</small>
</p>
<p
className="card-text"
dangerouslySetInnerHTML={{
__html: this.state.product.description
}}
/>
<div className="superflex add_to_cart_wrapper">
<button type="submit" name="add-to-cart" value={93} className="add_to_cart btn btn-success alt" onClick={this.props.cartUpdated}>Add to cart</button>
</div>
</div>
</div>
</div>
);
}
}
renderEmpty() {
return <NotFound />;
}
render() {
return (
<div className="container post-entry">
{this.state.product ? this.renderProduct() : this.renderEmpty()}
</div>
);
}
}
export default Product;
You can do something like this to achieve this. But the more clean solution which is suggested by the React JS is to use the React Context API.
Am sharing a link from the React JS documentation which exactly have the same scenario that you want to tackle.
https://reactjs.org/docs/context.html#updating-context-from-a-nested-component
And also since you are using the React pure component function so we can use the React hooks, you can have a look here at
https://reactjs.org/docs/hooks-reference.html#usestate
so in your code it should be like this
./Total-Context.js
export const TotalContext = React.createContext({
total: 0,
setTotal: () => {
},
});
./index.jsx
import { TotalContext } from './Total-Context';
const App = () => {
const [total, setTotal] = useState(0);
return (
<TotalContext.Provider value={{total, setTotal}}>
<div id="page-inner">
<Header currentTotal={total} />
<main id="content">
<Switch>
<Route
exact
path={`${Settings.path}products/:product`}
component={Product}
/>
</Switch>
</main>
<Footer />
</div>
</TotalContext.Provider>
);
};
and Now we can use the TotalContext consumer in the Product component and call the method to set the total method in the global context like this.
./Product.jsx
import { TotalContext } from './Total-Context';
const Product = () => (
<TotalContext.Consumer>
{({total, setTotal}) => (
<button
onClick={() => {setTotal(newTotal)}}
>
Update total
</button>
)}
</TotalContext.Consumer>
)
so after calling the click method the Header component should have the updated value of the total.
you can use redux to manage state across multiple components.
getting started with redux
To Answer this:
There is no way by the help of which you can pass state between two react components, as state is private to a component.
props can help you in this regard. Props also can't be passed from child to parent it can always be from parent to child.
There is a twist by the help of which you can achieve this, please follow the below article section: "How to pass Props from child to parent Component?" to get clear idea on this:
URL: https://www.robinwieruch.de/react-pass-props-to-component/

React Router 4 - Controlled Component not Un-Mounting on Switch

I'm trying to learn how to lift the state from <Child/> to <Parent/> and have the parent control user interactions done the child, (which receives state down as a prop) i.e show color and text.
I was able to lift the state up. However, when I switch back and forth between routes the <Parent/> component is not re-mounting and its state remains exactly how it was previously set by setState({})
const cars = [
{ name: "Ferrari", cost: "$9.000", color: "red", id: 1 },
{ name: "Porsche", cost: "$8.000", color: "black", id: 2 },
***
];
class Dealership extends Component {
state = {
cars,
isShow: {},
correctIndex: Math.floor(Math.random() * (cars.length - 1))
};
handleShuffle = () => {
this.setState({
cars: [...this.state.cars.sort(() => Math.random() - 0.5)],
isShow: {}
});
};
handleShow = car => {
const { isShow } = this.state;
console.log("isShow=", isShow);
this.setState(currentState => ({
isShow: { ...currentState.isShow, [car]: true }
}));
};
render() {
return (
<>
<Navigation />
<Routes
state={this.state}
shuffle={this.handleShuffle}
handleShow={this.handleShow}
// isShow={this.state.isShow}
/>
</>
);
}
}
export default withRouter(Dealership);
As mentioned above, the child <Car/>is receiving state down as props so that its user interaction can be controlled by one source of truth the parent <Dealership />
export default class Car extends Component {
render() {
const { cars, shuffle, isShow, handleShow, correctIndex } = this.props;
const correctCar = cars[correctIndex];
const car = cars.map(car => (
<CarList
// {...this.state}
isShow={isShow[car.name]}
key={car.id}
car={car.name}
guess={car.cost}
isCorrect={correctCar.cost === car.cost}
handleShow={handleShow}
/>
));
return (
<>
<Question key={correctCar.id} guess={correctCar.cost} />
<button
onClick={() => {
shuffle();
}}
>
go again
</button>
<ul className="car-list">{car}</ul>
</>
);
}
}
The <CarList/> is abstracted here:
// CarList.js
export const CarList = ({ isShow, isCorrect, car, handleShow, guess }) => {
function getColor() {
if (isShow) {
const showColor = isCorrect ? "green" : "red";
return showColor;
}
return "";
}
return (
<li onClick={() => handleShow(car)} className={getColor()}>
{car}
<span className={isShow ? "show" : "hide"}>{guess}</span>
</li>
);
};
Oddly (to me), when I switch to a route that holds its own local state i.e <Bike/>, everything works as expected (the state is back to original)
import React, { useState } from "react";
export const Bike = () => {
const [color, setColor] = useState(false);
function ChangeColor() {
setColor(true);
}
return (
<p onClick={ChangeColor}>
Click on the <span className={color ? "red" : " "}>Bike</span>
</p>
);
};
This is how I have my Routes setup:
// Navigation.JS
export const Navigation = () => (
<nav>
<ul>
<li>
<Link to="/">home</Link>
</li>
<li>
<Link to="/car-cost">car</Link>
</li>
<li>
<Link to="/bike">bike</Link>
</li>
</ul>
</nav>
);
// Routes.js
export const Routes = ({ state, shuffle, handleShow, isShow }) => (
<Switch>
<Route
path="/car-cost"
render={() => (
<Car
{...state}
shuffle={shuffle}
handleShow={handleShow}
// isShow={isShow}
/>
)}
/>
<Route path="/bike" render={() => <Bike />} />
<Route path="/" component={Home} />
</Switch>
);
I then wrapped my main app with <BrowserRouter /> as you see in totality plus the current misbehavior happening on this code sandbox
How can I switch between routes having <Car/> behave such as <Bike/>? i.e return to its original state. Also, am I lifting and controlling state correctly here?
Here the state are being saved in parent component. When the route changes then only child components are being remounted. So the state of parent component remains there throughout that routing.
You can keep the state in child component, which would reset the state after every unmount. However if you want to lift the state up and still reset the state, then you would have to do that in parent component.
A better way would be to monitor the route change in the parent component. If the route has changed then parent component should reset its state. In componentDidUpdate method of parent component, you can track the route change and reset the state like this
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
console.log('Route change! Reset the state');
this.setState({ isShow: {}})
}
}

Categories