How can I render different component using onClick() function in React? - javascript

I am new to React and creating a Tic-Tac-Toe game. I want to create a starting page with three options :
Start
Rules
Exit
On clicking the start button I want to render the component which consists of the original game. On clicking rules I want to render a page showing rules. I have created seperate components for the three buttons and also the game itself.
Screenshot-Start Page
Screenshot-Main Game
My Tic-Tac-Toe Repo

To redirect to a new page containing one of your component you can use react router :
https://v5.reactrouter.com/web/guides/quick-start
and use your button as a Link or use the useHistory hook in your onClick function
If you just want to render a component on the current page when you click on a button you can simply use a condition with a state like so :
...
const [isStart, setIsStart] = useState(false)
...
{isStart ? <Start> : <Button onClick={() => setIsStart(true)}>Start</Button>}

You have to use a React Router which is responsible for showing different components on different url paths, eg.:
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import LandingPage from './landing-page/LandingPage';
import Details from './details/Details';
const Router = () => {
return (
<React.Fragment>
<Switch>
<Route path={`/`} exact render={() => <LandingPage />} />
<Route path={`/details`} exact render={() => <Details />} />
</Switch>
</React.Fragment>
);
};
export default Router;
and then just redirects to those paths on click:
handleClick = (e) => {
e.preventDefault();
history.push('/results');
}
return (
<Button onClick={handleClick}>Results</Button>
);

Related

React Router 6 - useLocation Not Working Like I Expected [duplicate]

This question already has an answer here:
How to run a function when user clicks the back button, in React.js?
(1 answer)
Closed 5 months ago.
I'm new to React, so I'm sure I'm not understanding the use cases for useLocation - like what it is good for and what it is not intended for.
I'd like to have a method that a specific component can be aware of any location change included those from pushState. Note: I'm converting an Anuglar JS 1.0 code base that just used all query info in the hash. I'd like to use pushState browser feature in this rewrite.
Sample code below (I just have it as the single component in a new React app component:
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const RandLocation: React.FC = () => {
const location = useLocation();
useEffect(() => {
console.log('location: ', location);
}, [location]);
return (
<div>
<button
onClick={() => {const r = Math.random(); window.history.pushState({'rnd': r }, '', '/?rnd=' + r)}}>
Click Me</button>
<br/>
</div>
)
}
export default RandLocation;
I only see the useEffect run on load, and if I move forward or back using the browser buttons. But not when I click the "Click Me" button. What am I missing? Id like to keep this "awareness of location" as simple as possible within the React frontend code. Like is there a technique that works in apps regardless of if you have React Router routes defined?
I am using React version 17.0.2 and react-router-dom version 6.2.2
I think because the window.history.pushState call is outside of React's state management it react-router won't be aware of it. There used to be a way to listen for these events, but I'm not sure something equivalent exist in React Router 6.
You could use the useNavigate hook. Maybe something like:
import React, { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
const RandLocation = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
console.log("location: ", location);
}, [location]);
return (
<div>
<button
onClick={() => {
const r = Math.random();
//window.history.pushState({ rnd: r }, "", "/?rnd=" + r);
navigate("/?rnd=" + r, { state: { rnd: r } });
}}
>
Click Me
</button>
<br />
</div>
);
};
export default RandLocation;
One issue with this approach, is you'd have to set up a default route to catch anything that no route is defined for like this:
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="*" element={<WhereYouWantDefaultRoutesToGoTo />} />
</Routes>
</BrowserRouter>
You might also want to take a look at: https://stackoverflow.com/a/70095819/122201

React Router and browser history

I'm thoroughly lost and would like to ask for a recomendation on how to implement browser history inside my app.
With Router, all i have is a single component which gets assigned pages based on which page i'm on. pages and text inside app is acquired from an api, and whenever i click a button, the api gets called again.
<Router>
<Switch>
<Route to="/" component={Body} />
...
which probably doesnt even work as it should, because the Link tags are on the buttons, and they point to /page/number:
const renderPageNumbers = apiPagingSliced.map((links, index) => {
return <Link key={index} to={`/page/${links.label}`}>
<button key={index} id={links.label}
onClick={props.handleClick}
className={(links.active ? "mark-page" : "") + " " + (links.url === null ? "remove-btn" : "")}
>{links.label}
</button></Link>
}
)
i've managed to get it working so that i get "www.webpage.com/page/3" for example. But when i press back in browser, it only changes the url into previous page, doesn't do anything else. How do i implement a functional back/forward history function?
First you should add the route params example : "/:id"
<Route to="/some_page/:id" component={SomePage} />
Then import useHistory and UseParams from react-router :
import { useHistory, useParams } from "react-router-dom";
let { id } = useParams();
let history = useHistory();
<button onClick={() => history.push(`/some_page/${id}`)}> Go to page </button>
First you should add a route to the /page/(some number)
And this is done like this:
<Router>
<Switch>
<Route to="/" component={Body} />
<Route to="/page/:id" component={Page} />
...
And now in the Page component import a react router dom hook called useParams
import { useHistory } from 'react-router-dom'
const Page = () => {
const history = useHistory()
//pageID will be equal to the page number
return (
<div>
<button onClick={event => history.goBack`}>Go back!</button>
</div>
)
}

Why will history.push not redirect to a new component using react-router-dom?

I have 2 components both are exactly the same. one is redirected to when I click on a Navlink inside of my navbar that I created using react-bootstrap. The other component that is exactly the same won't load when I click on the html button that should redirect to that component. Please help me.
the html button and the function to redirect look like
const getProfile = async (member) => {
// const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
history.push('/member')
}
<button onClick={() => getProfile(p.publisher)}>Profile</button>
The routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route path="/member" exact component={Member} />
</Switch>
)
}
export default Routes
The component that I am trying to redirect to is exactly the same as one that is redirected to and working when I click on it from the navlink

React: Unable to navigate views with router after changing the state of Context (useContext)

I have a simple setup to test the use of the useContext hook, when you want to change the context value in child components.
A simple Context is defined in its own file like such:
import React from 'react'
const DataContext = React.createContext({})
export const DataProvider = DataContext.Provider
export default DataContext
Then I wrap my router in a provider in a component that exposes its state to use as a reference for the ContextProvider, as such:
import { DataProvider } from './dataContext.js'
export default function App(props) {
const [data, setData] = useState("Hello!")
const value = { data, setData }
const hist = createBrowserHistory();
return (
<DataProvider value={value}>
<Router history={hist}>
<Switch>
<Route path="/admin" component={Admin} />
<Redirect from="/" to="/admin/services" />
</Switch>
</Router>
</DataProvider>
)
}
Finally I have two Views that I am able to navigate between initially, one of them showcasing the context value, as well as containing a button to change it:
export default function EndpointView(props) {
const { data, setData } = useContext(DataContext)
return (
<div>
<h1>{data}!</h1>
<Button onClick={() => setData(Math.random())}>Update context state</Button>
</div>
)
}
The functionality seems to work, as the showcases text is updated.
The problem is, when I have clicked the button, I can no longer navigate in my navbar, even though the url is changing. Any ideas as to why?
This is showcased in this picture, where the url is corresponding to the top-most item in the side bar, even though we are stuck in the "endpoint view"-component.
Edit:
So the routing works by including a switch in the Admin layout:
const switchRoutes = (
<Switch>
{routes.map((prop, key) => {
if (prop.layout === "/admin") {
return (
<Route
path={prop.layout + prop.path}
component={prop.component}
key={key}
/>
);
}
return null;
})}
<Redirect from="/admin" to="/admin/services" />
</Switch>
);
Where the routes (which we .map) are fetched from another file that looks like this:
const dashboardRoutes = [
{
path: "/services",
name: "Services view",
icon: AccountBalance,
component: ServicesView,
layout: "/admin"
},
{
path: "/endpoint",
name: "Endpoint view",
icon: FlashOn,
component: EndpointView,
layout: "/admin"
}
];
export default dashboardRoutes;
I was able to solve this issue.
I suspect the problem was that updating the state reloaded the root router component which caused some issues.
Instead I moved the DataProvider tag one step down the tree, to wrap the switch in the Admin component.

React Routing to Same Component with Different URL

In my main class I have a nav bar with the options below:
<NavDropdown title="Search" id="collasible-nav-dropdown">
<NavDropdown.Item href="#/searchpage/p" onClick={this.dontEdit}>Find People</NavDropdown.Item>
<NavDropdown.Item href="#/searchpage/s" onClick={this.searchSchool}>Find Schools</NavDropdown.Item>
<NavDropdown.Item href="#/searchpage/w" onClick={this.dontEdit}>Find Work Places</NavDropdown.Item>
</NavDropdown>
These have a route which routes to the same component which then reads the parameter at the end of the URL and runs a different search depending on the value. For example 's' is search schools and 'p' is search people. If I navigate between the different search functions from the nav bar then it doesn't refresh to the new search. For example if I go from 'Find Schools' to 'Find Work' it stays on schools, but if I were to go direct to 'Find Work Places' then it goes there direct. Also if I navigate to the home page and back to another search then it works.
The route looks like:
<Route path="/searchpage/:type" render={props => (<SearchPage {...props} findPerson={this.findPerson} routeReset={this.routeReset} getPersonsByName={this.getPersonsByName} />)}/>
Is anyone able to advise how to get this to route as I want it to? The search component is like:
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import Flash from './components/flash';
import Search from "./components/search";
const searchtypes = {"p":"People","w":"Work Places","s":"Schools"};
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
type:this.props.match.params.type
}
}
componentDidMount(){
}
render() {
return (
<Container>
<Row>
<Col>
<h4>Search {searchtypes[this.state.type]}</h4>
<br/>
</Col>
</Row>
<Row><Col><Search {...this.props} type={this.state.type}/></Col></Row>
</Container>
);
}
}
export default SearchPage;
The Route's render prop doesn't remount when the matched route doesn't change, i.e. even when the route matches but the route param is different it won't re-render. Instead use the component prop.
react-router-dom component
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop (below).
<Route
path="/searchpage/:type"
component={props => (
<SearchPage
{...props}
findPerson={this.findPerson}
routeReset={this.routeReset}
getPersonsByName={this.getPersonsByName}
/>
)}
/>
An alternative to this is to implement the componentDidUpdate lifecycle function in SearchPage to detect when the route param prop updates and update the type stored in state. This way the component won't continually unmount/mount each time.
componentDidUpdate(prevProps) {
if (prevProps.match.params.type !== this.props.match.params.type) {
setState({
type: this.props.match.params.type,
});
}
}
Try this:
class SearchPage extends Component {
render() {
return (
<Container>
<Row>
<Col>
<h4>Search {searchtypes[this.props.match.params.type]}</h4>
<br/>
</Col>
</Row>
<Row><Col><Search {...this.props} type={this.props.match.params.type}/></Col></Row>
</Container>
);
}
}
The type data comes from props and therefore you should not persist it on the component state.
Note: Make sure you use react-router Link component it seems you use native a tags

Categories