react router dom link with params dont allways work - reactjs

I have written a simple search component with autosuggestion in react, it makes calls to the themoviedb. I am using react-router-dom and have defined a route param like this in app.js:
<Route path="/:id" component={SomeComponent} />
and the search component looks like this:
import React, { Component, Fragment } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import axios from "axios";
const SuggestionsResult = styled.ul`
text-decoration: none;
list-style: none;
text-align: left;
display: flex;
flex-direction: column;
width: 100%;
`;
const ResultItem = styled.li`
border-bottom: 0.0625rem solid hsla(0, 0%, 100%, 0.12);
padding: 10px 0 10px;
padding-left: 2px;
font-size: 1em;
cursor: pointer;
&:hover {
background: hsla(0, 0%, 100%, 0.12);
}
`;
export default class Search extends Component {
state = {
query: "",
results: [],
showSuggestions: false
};
handleInputChange = () => {
this.setState(
{
query: this.search.value
},
() => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query.length % 2 === 0) {
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=en-US&query=${
this.state.query
}&page=1&include_adult=false`
)
.then(({ data }) => {
this.setState({
results: data.results,
showSuggestions: !this.state.showSuggestions
});
});
}
} else if (!this.state.query) {
}
}
);
};
handleSuggestionClick = e => {
this.setState({ showSuggestions: false });
};
render() {
return (
<Fragment>
<input
placeholder="Search for a movie..."
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
{this.state.showSuggestions && (
<Suggestions
results={this.state.results}
handleSuggestionClick={this.handleSuggestionClick}
/>
)}
</Fragment>
);
}
}
const Suggestions = ({ results, handleSuggestionClick }) => {
const options = results.map(r => (
<ResultItem key={r.id}>
<Link onClick={handleSuggestionClick} to={`/${r.id}`}>
{r.title}
</Link>
</ResultItem>
));
return <SuggestionsResult>{options}</SuggestionsResult>;
};
My problem is when clicking on the link it changes the url but it stays on the same site. If I dont use react-router-dom Link component and only use a elements it works fine but everthing re-renders.
** Update
My react-router code in app.js
<Router>
<Switch>
<Route exact path="/" component={MoviesList} />
<Route path="/:id" component={MovieDetail} />
</Switch>
</Router>

The Suggestions component doesn't receive the Router prop and hence it causes this issue. Wrap your Suggestions component with withRouter HOC. Also make sure that the Search component is rendered as a child or Router component
import React, { Component, Fragment } from "react";
import { Link, withRouter } from "react-router-dom";
import styled from "styled-components";
import axios from "axios";
const SuggestionsResult = styled.ul`
text-decoration: none;
list-style: none;
text-align: left;
display: flex;
flex-direction: column;
width: 100%;
`;
const ResultItem = styled.li`
border-bottom: 0.0625rem solid hsla(0, 0%, 100%, 0.12);
padding: 10px 0 10px;
padding-left: 2px;
font-size: 1em;
cursor: pointer;
&:hover {
background: hsla(0, 0%, 100%, 0.12);
}
`;
export default class Search extends Component {
state = {
query: "",
results: [],
showSuggestions: false
};
handleInputChange = () => {
this.setState(
{
query: this.search.value
},
() => {
if (this.state.query && this.state.query.length > 1) {
if (this.state.query.length % 2 === 0) {
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=en-US&query=${
this.state.query
}&page=1&include_adult=false`
)
.then(({ data }) => {
this.setState({
results: data.results,
showSuggestions: !this.state.showSuggestions
});
});
}
} else if (!this.state.query) {
}
}
);
};
handleSuggestionClick = e => {
this.setState({ showSuggestions: false });
};
render() {
return (
<Fragment>
<input
placeholder="Search for a movie..."
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
{this.state.showSuggestions && (
<Suggestions
results={this.state.results}
handleSuggestionClick={this.handleSuggestionClick}
/>
)}
</Fragment>
);
}
}
const Suggestions = withRouter(({ results, handleSuggestionClick }) => {
const options = results.map(r => (
<ResultItem key={r.id}>
<Link onClick={handleSuggestionClick} to={`/${r.id}`}>
{r.title}
</Link>
</ResultItem>
));
return <SuggestionsResult>{options}</SuggestionsResult>;
});

Related

Unable to view custom sidebar component created with react router in storybook

I am quite new to React and have been trying to create a custom sidebar component. Based on my understanding by going through different tutorials, this is what I have so far:
react-router-dom version: ^6.3.0
storybook: ^6.1.17
These are my components
sidebar.jsx
const SidebarNav = styled.nav`
background: #15171c;
width: 250px;
height: 100vh;
display: flex;
justify-content: center;
position: fixed;
top: 0;
right: ${({ sidebar }) => (sidebar ? "0" : "-100%")};
transition: 350ms;
z-index: 10;
`;
const SidebarWrap = styled.div`
width: 100%;
`;
const Sidebar = () => {
const [sidebar, setSidebar] = useState(true);
return (
<>
<IconContext.Provider value={{ color: "#fff" }}>
<SidebarNav sidebar={sidebar}>
<SidebarWrap>
{sideBarData.map((item, index) => {
return <SubMenu item={item} key={index} />;
})}
</SidebarWrap>
</SidebarNav>
</IconContext.Provider>
</>
);
};
export default Sidebar;
sidebardata.jsx
export const sideBarData = [ {
title: "Question 4",
path: "/test"
},
// {
// title: "Question 5",
// path: "/testing"
// }
];
sidebarsubmenu.jsx
import React, { useState } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
const SidebarLink = styled(Link)`
display: flex;
color: #e1e9fc;
justify-content: space-between;
align-items: center;
padding: 20px;
list-style: none;
height: 60px;
text-decoration: none;
font-size: 18px;
&:hover {
background: #252831;
border-left: 4px solid green;
cursor: pointer;
}
`;
const SidebarLabel = styled.span`
margin-left: 16px;
`;
const DropdownLink = styled(Link)`
background: #252831;
height: 60px;
padding-left: 3rem;
display: flex;
align-items: center;
text-decoration: none;
color: #f5f5f5;
font-size: 18px;
&:hover {
background: green;
cursor: pointer;
}
`;
const SubMenu = ({ item }) => {
const [subnav, setSubnav] = useState(false);
const showSubnav = () => setSubnav(!subnav);
return (
<>
<SidebarLink to={item.path}
onClick={item.subNav && showSubnav}>
<div>
{item.icon}
<SidebarLabel>{item.title}</SidebarLabel>
</div>
<div>
{item.subNav && subnav
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</SidebarLink>
{subnav &&
item.subNav.map((item, index) => {
return (
<DropdownLink to={item.path} key={index}>
{item.icon}
<SidebarLabel>{item.title}</SidebarLabel>
</DropdownLink>
);
})}
</>
);
};
export default SubMenu;
BookSideBarLayout.jsx
import React from 'react';
import Sidebar from './BookSideBar';
function Layout(props) {
return (
<div>
<div style={{display: "flex"}}>
<Sidebar/>
</div>
</div>
);
}
export default Layout;
BookSidebarRoutes.jsx
import React from "react";
import {BrowserRouter, Route,Routes} from "react-router-dom";
import Support from "./Support";
import Layout from "./BookSideBarLayout";
function MyRoutes() {
return (
<BrowserRouter>
<Layout/>
<Routes>
{/* <Route path="/" element={<Sidebar/>}/> */}
<Route path="/test" element={<Support/>}/>
</Routes>
</BrowserRouter>
);
}
export default MyRoutes;
The story that I created was as below
import { storiesOf } from '#storybook/react';
import StoryRouter from 'storybook-react-router';
import MyRoutes from '../../src/components/BookSidebarRoutes';
// export default {
// title: 'Side Bar',
// //decorators : [(Story) => (<MemoryRouter><Story/></MemoryRouter>)]
// };
storiesOf('Params', module)
.addDecorator(StoryRouter())
.add('params', () => (
<MyRoutes/>
));
export const Default = () => <MyRoutes />;
After I run my story above, I keep getting the error as below:
A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.**
I also tried Switch in the MyRoutes component but that too didn't work. Any guidance in this will be really helpful. Thank you in advance

React - Typescript - How to function as prop to another component

There are three components Toggle, ToggleMenu, Wrapper
The toggle should be universal, and used for different functions
The Wrapper should only change background color when toggle is on
The question is how to pass the function that responcible for changing color to togglemenu, that it executs when switch toggle
App
import React from 'react';
import { Wrapper } from './containers/wrapper';
import { Button } from './components/buttons/button';
import { ToggleMenu } from './components/settings/settings';
const App = () => {
return (
<>
<Wrapper>
<Button />
<ToggleMenu />
</Wrapper>
</>
)
}
export default App;
ToggleMenu
import styled from "styled-components";
import { Toggle } from "../buttons/toggle";
import { WrapperProps } from "../../containers/wrapper";
import { test } from "./test"
const SettingsContainer = styled.div`
margin: auto;
width: 50%;
height: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
`;
const Container = styled.div`
height: 50%;
width: 50%;
display: flex;
justify-content: center;
flex-direction: column;
background-color: lime;
`;
const TogCon = styled.div`
margin: 0.5em;
`;
export const ToggleMenu = (props: WrapperProps) => {
return (
<SettingsContainer>
<Container>
<TogCon>
<Toggle toggleText={"Dark mode"} onChange={props.handleTheme}/>
</TogCon>
<TogCon>
<Toggle toggleText={"Sth"} onChange={() => {console.log("Sth")}}/>
</TogCon>
</Container>
</SettingsContainer>
);
};
Wrapper
import React, { useState } from "react";
import styled from "styled-components";
export type WrapperProps = {
children?: React.ReactNode;
color?: string;
handleTheme?: () => void;
};
const Container = styled.div<WrapperProps>`
width: 100%;
height: 100%;
top: 0;
left: 0;
position: fixed;
background-color: ${props => props.color }
`
export const Wrapper = ({ children }: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
return(
<Container
color={theme}
handleTheme={handleTheme}
> { children }
</Container>
);
}
Was solved with React.cloneElement
export const Wrapper = (props: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
const childrenWithProps = React.Children.map(props.children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { handleTheme });
}
return child;
});
return(
<Container color={theme} >
{ childrenWithProps }
</Container>
);
}

Strange stuff with useState, useEffect and fetch

I'm trying to fetch some data inside useEffect and when the data is received I want to set a certain state with useState. Data correctly returns from the server. However this doesn't work. Here's the code:
const [sharingLink, setSharingLink] = React.useState(null);
React.useEffect(() => {
client.query({
query: queryGetReferalData
}).then(result => {
console.warn(result); // correct response
setSharingLink(result.data.referralsystem.shareUrl);
console.warn(sharingLink); // null
});
}, []);
Here's the whole component:
import React from 'react';
import styled from 'styled-components';
import { i18n } from 'Helpers';
import { Button } from './Button';
import { ButtonLink } from './ButtonLink';
import { Heading } from './Heading';
import { Input } from './Input';
import Copy from './icons/Copy';
import Facebook from './icons/Facebook';
import Twitter from './icons/Twitter';
import WhatsApp from './icons/WhatsApp';
import client from '#client/apollo';
import queryGetReferalData from './schemas/queryGetReferalData.graphql';
const Root = styled.div`
padding: 48px;
padding-top: 32px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0px 10px 30px rgba(27, 50, 85, 0.1);
border-radius: 4px;
background-color: #FFFFFF;
`;
const Pane = styled.div`
`;
const Row = styled.div`
display: flex;
& > * + * {
margin-left: 10px;
}
`;
export const Form = () => {
const [sharingLink, setSharingLink] = React.useState(null);
const facebookSharingLink =
sharingLink && `https://www.facebook.com/sharer/sharer.php?${encodeURIComponent(sharingLink)}`;
const twitterSharingLink =
sharingLink && `http://www.twitter.com/share?url=${encodeURIComponent(sharingLink)}`;
const whatsAppSharingLink =
sharingLink && `whatsapp://send?text=${encodeURIComponent(sharingLink)}`;
React.useEffect(() => {
client.query({
query: queryGetReferalData
}).then(result => {
console.warn(result);
setSharingLink(result.data.referralsystem.shareUrl);
console.warn(sharingLink);
});
}, []);
return (
<Root>
<Pane>
<Heading>
{ i18n._('Your invitational link') }
</Heading>
<Row>
<Input disabled={sharingLink === null} value={sharingLink} />
<Button icon={<Copy />}>
{ i18n._('COPY') }
</Button>
</Row>
</Pane>
<Pane>
<Heading>
{ i18n._('Or share via social') }
</Heading>
<Row>
<ButtonLink
backgroundColor='#5A79B5'
icon={<Facebook />}
href={facebookSharingLink}
>
{ i18n._('Facebook') }
</ButtonLink>
<ButtonLink
backgroundColor='#52A6DB'
icon={<Twitter />}
href={twitterSharingLink}
>
{ i18n._('Twitter') }
</ButtonLink>
<ButtonLink
backgroundColor='#0DC455'
icon={<WhatsApp />}
href={whatsAppSharingLink}
>
{ i18n._('WhatsApp') }
</ButtonLink>
</Row>
</Pane>
</Root>
);
};
The component also renders like sharingLink is null.
Why is this happening?
What do I do to make this work?
I had some code that was adding to the DOM in the parent component. When I remove it, everything works.
Adding to the DOM in the component, doesn't matter if it's into useEffect or not, somehow messes up hooks even though you add HTML to something completely unrelated to react I guess.
I had this structure:
<body>
<div id="page">
<div id="root">
<CustomReactElement />
</div>
</div>
<body>
The code inside CustomReactElement was adding markup to the 'page'. I changed the code from setting an innerHTML to appendChild() and everything worked.

Why is my navbar "inexistant" for my other components?

I just created a react app. First thing first, I wanted to make a navbar for the left side that will be accessible on every page. So far so good, it's working well, my issue arrises when I started to create my first page: it keeps clipping under my navbar, and nothing I do gets it out of under the bar, this is driving me insane. Here's the current state of the code...
App.js
class App extends Component {
render() {
return(
<Router>
<SideNavBar />
<Switch>
<Route exact path={"/"} component={HomePage} />
</Switch>
</Router>
);
}
}
Navbar
class SideNavBar extends Component {
constructor(props) {
super(props);
this.state = {
currentPath: props.location.pathname,
};
}
onClick (path) {
this.setState({ currentPath: path });
}
render() {
const { currentPath } = this.state;
const navItems =
[
{
path: "/",
css: "fas fa-home"
}, {
path: "/user",
css: "fas fa-user"
},
];
return(
<StyledSideNavBar>
{
navItems.map((item, index) => {
return (
<NavItem
item={item}
active={item.path === currentPath}
onClick={this.onClick.bind(this)}
key={index}
/>
);
})
}
</StyledSideNavBar>
);
}
}
Styled Navbar
const StyledSideNavBar = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
position: fixed;
height: 100vh;
width: 5rem;
top: 0;
left: 0;
padding-top: 1.5rem;
background-color: #EEEEEE;
`;
Navitem
class NavItem extends Component {
render() {
const { item, active, onClick } = this.props;
return(
<StyledNavItem active={active}>
<Link to={item.path} className={item.icon} onClick={() => onClick(item.path)} />
</StyledNavItem>
);
}
}
Styled Navitem
const StyledNavItem = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
margin-bottom: 1.5rem;
a {
font-size: 2.7em;
color: ${(props) => props.active ? "#8394F5" : "black"};
:hover {
opacity: 0.7;
text-decoration: none;
}
}
`;
HomePage
class HomePage extends Component {
render() {
return (
<StyledHomePage>
{"Hi {user}!hhhhhhhhhhhhhhhhhhhhhh"}
</StyledHomePage>
);
}
}
Styled HomePage
const StyledHomePage = styled.div`
display: "flex",
margin: "5rem 5rem 0 5rem"
`;
The problem arises when you give postion: fixed to your NavBar, instead you should create a fluid design and remove fixed position. Let me know if you need more help in it.

react all list items get re-rendered

I have my state structured like this. It's an object with multiple fields inside of it. For certain purposes, I cannot modify the structure of the state. Here's my component which renders the entire List
import React, { Component } from 'react'
import styled from 'styled-components';
import FoodListItem from '../Food-List-Item'
const Wrapper = styled.div`
width: 300px;
max-width: 300px;
`
const Button = styled.button`
width: 300px;
text-align: center;
padding: 1em;
border: 1px solid #eee;
background-color: #ffffff;
text-transform: uppercase;
font-size: 1em;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.45s ease-in-out, border 0.45s ease-in-out;
:hover {
background-color: #eee;
border: 1px solid black;
}
`
class FoodList extends Component {
state = {
data: {
e5d9d9f5: {
label: 'ice cream',
isDelicious: true,
isHealthy: false,
},
a9ba692b: {
label: 'pizza',
isDelicious: true,
isHealthy: false,
},
ze128a47: {
label: 'spinach',
isDelicious: false,
isHealthy: true,
},
},
}
renderListItems = () => {
const { data } = this.state
return Object.keys(data).map((key) => {
return <FoodListItem
key={key}
{...data[key]}
id={key}
handleDecliousChange={this.handleDecliousChange}
handleHealthyChange={this.handleHealthyChange}
/>
})
}
handleDecliousChange = (id) => {
this.setState(state => ({
data: {
...state.data,
[id]: {
...state.data[id],
isDelicious: !state.data[id].isDelicious
}
}
}))
}
handleHealthyChange = (id) => {
this.setState(state => ({
data: {
...state.data,
[id]: {
...state.data[id],
isHealthy: !state.data[id].isHealthy
}
}
}))
}
handleShowAppState = () => {
console.log(this.state.data)
}
render() {
return (
<Wrapper>
{this.renderListItems()}
<Button type="button" onClick={this.handleShowAppState}>Show App State</Button>
</Wrapper>
)
}
}
export default FoodList;
Here's the component which renders a single list item
import React from 'react'
import styled from 'styled-components'
const Title = styled.p`
text-transform: uppercase;
font-weight: bold;
`
const Item = styled.div`
padding: 1em 1em 1em 0em;
padding-left: ${props => props.isDelicious ? '30px': '0px'}
margin-bottom: 2em;
background-color: ${props => props.isHealthy ? 'green' : 'gray'};
transition: background-color 0.45s ease-in-out, padding-left 0.45s ease-in-out;
color: #ffffff;
`
class FoodListItem extends React.PureComponent {
deliciousFn = () => {
this.props.handleDecliousChange(this.props.id)
}
healthyFn = (id) => {
this.props.handleHealthyChange(this.props.id)
}
render() {
console.log('render called', this.props.label);
const {
id,
label, isDelicious, isHealthy,
handleDecliousChange, handleHealthyChange
} = this.props
return (
<Item isHealthy={isHealthy} isDelicious={isDelicious}>
<Title>{label}</Title>
<div>
<input type="checkbox" checked={isDelicious} onChange={this.deliciousFn} />
<label><code>isDelicious</code></label>
</div>
<div>
<input type="checkbox" checked={isHealthy} onChange={this.healthyFn} />
<label><code>isHealthy</code></label>
</div>
</Item>
)
}
}
export default FoodListItem
Whenever I click on a single list item, it re-renders all of them. Is there a way to avoid this? Ideally, only the row which was clicked on should re-render.
You should implement shouldComponentUpdate to handle component’s output in FoodListItem:
Use shouldComponentUpdate() to let React know if a component’s output
is not affected by the current change in state or props. The default
behavior is to re-render on every state change, and in the vast
majority of cases you should rely on the default behavior.
class FoodListItem extends React.Component {
//...
shouldComponentUpdate(nextProps) {
return (
this.props.isDelicious !== nextProps.isDelicious ||
this.props.isHealthy !== nextProps.isHealthy
)
}
//...
}
Reference: https://reactjs.org/docs/react-component.html#shouldcomponentupdate
Or consider using the PureComponent: https://reactjs.org/docs/react-api.html#reactpurecomponent

Resources