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
Related
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>
);
Consider this example
export function InsideHoc(props){
const [A, setA] = useState(false);
return({if(A) && (<h1>Print something</h1>)});
}
In another file
import {A, setA} from './inside-file';
function ToggleFromOutside(){
return(<p onClick={setA(!A)}>Verify</p>);
}
Can setA be exposed outside so that the state of this component be changed from outside? I know this can be done through redux. But without using this, is there a way to change the state of one component?
Structure is like this
import {withCreateHOC} from './external';
import childComponent from './child';
class A extends React.Component {
render(){
<Another menus={(item) => <MenuItems object={item} />}
/>
}
}
export default withCreateHOC(A, {custom: childComponent, title: 'Add'});
//withCreateHOC renders modal here as well as it has a button to toggle the state. Same state should be used from below function
function MenuItems(){
return(<button onClick={displayModal}>)
}
Yes.
You can lift the state in that case. This works good if you don't need to pass down the setState to far down the tree.
If you don't want to pass the setState function all the way down the React element tree, you will need to use context, redux or some other state handling.
Example state lift
export function Parent(){
const [message, setMessage] = useState("Hello World");
return (
<>
<Child1 message={message} />
<Child2 changeMessage={setMessage} />
</>
);
}
// Can be in other file
function Child1(props){
return(<p>{props.message}</p>);
}
// Can be in other file
function Child2(props){
return(
<a onClick={() => props.changeMessage("Changed")}>
I can change things in other components.
</a>
);
}
Example of React tree with shared context/redux
<WithRedux>
<App>
<Navigation />
<Modal />
<PageRenderer />
<SomeList>
<ListItem />
<ListItem />
</Somelist>
</App>
<WithRedux>
All the children of the WithRedux component can access the state and modify it.
(PS: You can wrap the App with the HOC withRedux etc, this example is just for visualization)
So I have a global nav bar component that sits at the home screen and app screen and a music playing component. On click of one of the items in the nav bar I want to mute something on the music component.
Currently, to mute the music etc I'm using state.
So the way I've got this setup is to pass through an object as props and set that as state like so:
const obj = {
playing: false,
toggleButtonText: 'Play',
muteActive: false,
};
And I pass this as props into my components:
<Router>
<div>
<Nav stateVal={obj} />
<Route exact path="/" render={() => <Start />} />
<Route path="/app" render={() => <App stateVal={obj} />} />
<Modal />
</div>
</Router>
Then in each of my components, I do:
constructor(props) {
super(props);
this.state = this.props.stateVal;
}
So the props are set as the state of the component.
My problem is that I want one component to update the props and the update the state of the other component but I have no idea how I'm going to do that?
Could anyone give me a bit of help or pointers?
Assigning props to state in constructor is an anti-pattern because if the props change later on then the state isn't going to change.
Have the component update the props of the parent and then pass the props down the other child.
If you can't do this for some reason then you should look into Redux, Flux or MobX to handle the state.
Example
class Parent extends React.Component {
setMusicActive = (muteActive) => {
this.setState({ muteActive });
}
<ChildOne muteActive={this.state.muteActive} setMusicActive={this.setMuteActive} />
<ChildTwo muteActive={this.state.muteActive} setMusicActive={this.setMuteActive} />
}
class ChildOne extends React.Component {
someOtherFunction = () => {
this.props.setMuteActive(!this.props.muteActive);
}
}
Updates the value in one place and you can use it in the children.
I'm using Redux with redux-simple-router.
Here's what I'm trying to do. A user hits a URL like so:
http://localhost:3000/#/profile/kSzHKGX
Where kSzHKGX is the ID of the profile. This should route to Profile container filled out with the details of the profile with id kSzHKGX.
My routes look like this:
export default (
<Route path="/" component={App}>
...
<Route path="profile" component={Profile} />
...
</Route>
)
So hitting the above link would give me Warning: [react-router] Location "undefined" did not match any routes
My container looks like this:
#connect(
state => state.profile,
dispatch => bindActionCreators(actionCreators, dispatch)
)
export class Profile extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
const { getProfileIfNeeded, dispatch } = this.props
getProfileIfNeeded()
}
render() {
return (
<section>
...
</section>
)
}
}
So normally my container would just be populated from the state as usual in Redux.
Basically I need to have a way of doing some wildcard in the route. Than I need to pass the URL to the action that would pull up the right profile from an API. The question is, is it doable with react-simple-router? Can I do that somehow using UPDATE_PATH? Would it be the proper Redux way? Or should I use something else?
Following Josh David Miller's advice, I made my route look like so:
<Route path="admin/profile/:id" component={Profile} />
Than my container got this method to get the profile from API:
componentWillMount() {
const { getProfile, dispatch } = this.props
getProfile(this.props.params.id)
}
And this to cleanup (without it I would have the previous profile display for split second on component load - before I hit API in componentWillMount)
componentWillUnmount() {
this.props.unmountProfile()
}
Update:
As an alternative to the cleanup, I'm considering using the Container Component Pattern. Basically have the outer component do the data fetching and passing the data to the inner component as a prop.
I'm having trouble overcoming an issue with react router. The scenario is that i need to pass children routes a set of props from a state parent component and route.
what i would like to do is pass childRouteA its propsA, and pass childRouteB its propsB. However, the only way i can figure out how to do this is to pass RouteHandler both propsA and propsB which means every child route gets every child prop regardless of whether its relevant. this isnt a blocking issue at the moment, but i can see a time when i'd be using the two of the same component which means that keys on propA will overwritten by the keys by the keys of propB.
# routes
routes = (
<Route name='filter' handler={ Parent } >
<Route name='price' handler={ Child1 } />
<Route name='time' handler={ Child2 } />
</Route>
)
# Parent component
render: ->
<div>
<RouteHandler {...#allProps()} />
</div>
timeProps: ->
foo: 'bar'
priceProps: ->
baz: 'qux'
# assign = require 'object-assign'
allProps: ->
assign {}, timeProps(), priceProps()
This actually works the way i expect it to. When i link to /filters/time i get the Child2 component rendered. when i go to /filters/price i get the Child1 component rendered. the issue is that by doing this process, Child1 and Child2 are both passed allProps() even though they only need price and time props, respectively. This can become an issue if those two components have an identical prop name and in general is just not a good practice to bloat components with unneeded props (as there are more than 2 children in my actual case).
so in summary, is there a way to pass the RouteHandler timeProps when i go to the time route (filters/time) and only pass priceProps to RouteHandler when i go to the price route (filters/price) and avoid passing all props to all children routes?
I ran into a similar issue and discovered that you can access props set on the Route through this.props.route in your route component. Knowing this, I organized my components like this:
index.js
React.render((
<Router history={new HashHistory()}>
<Route component={App}>
<Route
path="/hello"
name="hello"
component={views.HelloView}
fruits={['orange', 'banana', 'grape']}
/>
</Route>
</Router>
), document.getElementById('app'));
App.js
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>{this.props.children}</div>;
}
}
HelloView.js
class HelloView extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<ul>
{this.props.route.fruits.map(fruit =>
<li key={fruit}>{fruit}</li>
)}
</ul>
</div>;
}
}
This is using react-router v1.0-beta3. Hope this helps!
Ok, now that I'm understanding your issue better, here's what you could try.
Since your child props are coming from a single parent, your parent component, not react-router, should be the one managing which child gets rendered so that you can control which props are passed.
You could try changing your route to use a param, then inspect that param in your parent component to render the appropriate child component.
Route
<Route name="filter" path="filter/:name" handler={Parent} />
Parent Component
render: function () {
if (this.props.params.name === 'price') {
return <Child1 {...this.getPriceProps()} />
} else if (this.props.params.name === 'time') {
return <Child2 {...this.getTimeProps()} />
} else {
// something else
}
}
In child component, insted of
return <div>{this.props.children}</div>
You may merge props with parent
var childrenWithProps = React.cloneElement(this.props.children, this.props);
return <div>{childrenWithProps}</div>
React.cloneElement can be used to render the child component and so as pass any data which is available inside the child route component which is defined in the route.
For eg, here I am passing the value of user to the react childRoute component.
{React.cloneElement(this.props.childRoute, { user: this.props.user })}