i'm working on react router project (im beginner) to improve my skillz.
my problem:
I have a JSON file (Dragon ball Z). When i click on characters (like goku) i want to show his biography.
Actually when i click on goku every biography of each characters are showed.
I know how to do it with function component (useLocation ect..) but i'm totally stuck wicth class component because i can't use hooks in it, what is the good way to do it ?
here is the project :
DBZ REACT ROUTES
Thanks
Using react-router-dom v6 we can use useParams to read the params key within function component.
With class component you can write an HOC function to archive the same thing
a higher-order component is a function that takes a component and returns a new component
import { useParams } from 'react-router-dom'
function withParams(Component) {
return props => <Component {...props} params={useParams()} />;
}
class Charbio extends React.Component {
componentDidMount() {
let { id } = this.props.params;
// get the bio by id here
}
render() {
return API.map((element) => {
return <p>{element.bio}</p>;
});
}
}
export default withParams(Charbio);
Related
This question already has an answer here:
Problem in redirecting programmatically to a route in react router v6
(1 answer)
Closed last year.
I want to programatically redirect the user to a loading page while I await for a response from the server api. I'm trying to do this inside a class component
The code I've got looks more or less like this:
class App extends Component {
constructor(props){
super(props);
}
handleSubmit = () => {
useNavigate("/loading")
}
}
render() {
return (
<div>
<button onClick={this.handleSubmit}>
Upload!
</button>
</div>
);
}
}
export default App;
The thing is that nothing happens when i click the "Upload!" button. I've read that useNavigate cannot be used inside a class component but I'm not sure how I could implement this differently.
I guess my question is, how can I use useNavigate inside a class component?
EDIT:
Thanks for your responses. I finally decided to convert my code to a function using these steps: https://nimblewebdeveloper.com/blog/convert-react-class-to-function-component
It now works like a charm.
Your clarification is correct, useNavigate() is a hook and therefore can only be used in a functional component. I'm thinking as an alternative you can wrap your App with withRouter, a HOC that gives the wrapping component access to the match, location, and history objects. From there, you can update the location with history.push('/loading').
Please see here for more information on history.
You cannot use useNavigate which is a react hook inside of class component.
you can by the way use react-router-dom which provide different way to manipulate browser url.
Create a functional component as a Wrapper
import { useNavigate } from 'react-router-dom';
export const withRouter = (ClassComponent) => {
const RouterWrapper = (props) => {
const navigate = useNavigate();
return (
<ClassComponent
navigate={navigate}
{...props}
/>
);
};
return RouterWrapper;
};
Then in your App.js, export it by wraping with the functional component
import { withRouter } from './wrapper';
class App extends Component {
constructor(props){
super(props);
}
handleSubmit = () => {
useNavigate("/loading")
}
render() {
return (
<div>
<button onClick={this.handleSubmit}>
Upload!
</button>
</div>
);
}
}
export default withRouter(App);
I'm trying to have access to an element from an array of objects in the React state. This array is the result of a fetch command. I know that I need to write a "map" ,but the map will result in getting the whole elements of the array.
Here's the code I have written :
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
// function App() {
// } instead we'll use class component
class App extends Component{
constructor(){
super();
this.state = {
guys:[]
}
};
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(response=>response.json())
.then(user=>this.setState({guys:user}))
}
render(){
//we'll add the return part from the stateless component
return (
<div className="App">
{this.state.guys.map(guy=>(
<h2 > {guy.email} </h2>
))}
</div>
);
}
}
export default App;
The result of the code above is :
Sincere#april.biz
Shanna#melissa.tv
Nathan#yesenia.net
Julianne.OConner#kory.org
Lucio_Hettinger#annie.ca
Karley_Dach#jasper.info
Telly.Hoeger#billy.biz
Sherwood#rosamond.me
Chaim_McDermott#dana.io
Rey.Padberg#karina.biz
However,I would like to access to only one element,for example, the ideal outcome must be Shanna#melissa.tv.
I have tried <h1>this.state.guys[1].email</h1>, but this approach seems to be ineffective and I know I should do sth else. Do you have any idea on how to access and manipulate only one (or some) of the states?
I have a wizard component that is built around a class holding the questions in an array and passing the current question to a Wizard component.
I wish for each question to have a corresponding route.
The question object inside the array:
[{
route: 'service-option',
getComponent: props => {
return <QuestionOne {...props}/>;
},
...
},
...
]
I'm trying to render a memoized react Route component based on the question prop passed to the Wizard like so:
const memoizedQuestionRoute = useMemo(() => {
history.push(`wizard/${question.route}`);
const component = React.cloneElement(question.getComponent(...), {...});
return <Route path={`wizard/${question.route}`} render={props => component}/>;
}, [question]);
and render it in a div like so:
<div>
{memoizedQuestionRoute}
</div>
Problem is that the route is not even rendering and I don't see it inside the div. Any ideas why and how to solve this?
if you haven't decided yet. Get this in component.
import { __RouterContext as RouterContext } from 'react-router-dom';
import { useContext } from 'react';
/**
* useRouter hook for react-router
*/
const useRouter = () => useContext(RouterContext);
export default useRouter;
New to React, in the following code I am passing data between two components via the parent. Flow is from Search to the parent App then to another child Sidebar. I am able to send to both from Search to App and App to Sidebar individually but for some reason setState is not behaving as expected making the link to trigger a refresh of <Search updateMenu={this.handleSearchResult} /> as you can see in the console.log code comments below:
import React, { Component } from 'react';
import './App.css';
import Search from './Search';
import Sidebar from './Sidebar';
class App extends Component {
constructor() {
super()
this.state = {
menu: []
}
}
handleSearchResult = (array) => {
// always the correct value
console.log('in ', array);
this.setState( {menu: menuList})
// 1st call : empty
// 2nd call : previous value not showing on 1st call + new value as expected
// does not trigger <Sidebar list={this.state.menu}/>
console.log('out', this.state.menu);
}
render() {
return (
<div className="App">
// not refreshing
<Search updateMenu={this.handleSearchResult} />
<Sidebar list={this.state.menu}/>
</div>
);
}
}
export default App;
Logging this.setState(). Is not so straight forward. this.setState() is asynchronus.
Here is a reference on Medium.
I have recently encountered an issue regarding the usage of one of my costum components. I have created a "Chargement" (Loading in French) Component for a project I am working on.
This component is a simple circular spinner with a dark background that when displayed, informs the user that an action is going on.
import React, {Fragment} from 'react';
import { CircularProgress } from 'material-ui/Progress';
import blue from 'material-ui/colors/blue';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const styles = theme => ({
chargement: {
position: 'fixed',
left: '50%',
top: '50%',
zIndex: 1
}
});
class Chargement extends React.Component {
render () {
const { classes } = this.props;
if (this.props.chargement) {
return (
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
);
} else {
return null;
}
}
}
const mapStateToProps = (state) => {
return {
chargement: state.App.chargement
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
}, dispatch);
};
Chargement.propTypes = {
classes: PropTypes.object.isRequired
};
let ChargementWrapped = withStyles(styles)(Chargement);
export default connect(mapStateToProps, mapDispatchToProps)(ChargementWrapped);
This component is displayed based on a boolean variable in my redux Store called "chargement".
It works like a charm whenever I am using it to make api call and load data. However, one of the components in my Web App takes quite a bit of time to render (1-2 seconds). This component renders a pretty big list of data with expansion panels. I tried to set my display variable based on the componentWillMount and componentDidMount functions.
class ListView extends React.Component {
componentWillMount () {
this.props.setChargement(true);
}
componentDidMount () {
this.props.setChargement(false);
}
However with this particular case the "chargement" component never displays.
I also tried to create a "Wrapper Component" in case the issue came from my "chargement" component being somewhat related to the re-rendered component as a children. :
export default class AppWrapper extends React.Component {
render () {
return (
<Fragment>
<Reboot />
<EnTete />
<Chargement />
<App />
</Fragment>
);
}
}
The "App " component is the one that takes a few seconds to render and that I am trying to implement my "chargement" component for. I am pretty sure this as to do with the component lifecycle but everything I tried so far failed.
My current stack is : React with Redux and MaterialUi
What am I missing ?
Thanks for your help!
Ps: You might want to check the explanation and precision I added on the main answer comments as they provide further context.
Not sure if I understood correctly, but I think the problem is simply your API call takes more time than your component mounting cycle, which is totally normal. You can solve the problem by rearranging a bit the places where to put the IO.
Assuming you are making the API call from AppWrapper, dispatch the Redux action in componentDidMount i.e. fetchListItems(). When the API call resolves, the reducer should change its internal loading value from true to false. Then, AppWrapper will receive chargement as a prop and its value will be false. Therefore, you should check what this value is in AppWrapper's render method. If the prop is true, you render the Chargement component or else, render ListView.
Also, try always to decouple the IO from the view. It's quite likely that you'll need to reuse Chargement in other situations, right? Then, make it a simple, generic component by just rendering the view. Otherwise, if you need to reuse the component, it will be coupled to one endpoint already. For this, you can use a Stateless Functional Component as follows:
const Chargement = () =>
<Fragment>
<div className='loadingicon'>
<CircularProgress size={80} style={{ color: blue[500] }}/>
</div>
<div className='loadingBackground'/>
</Fragment>
I found a way to fix my issue that does not involve the use of the "chargement" component like I had initially planned. The issue revolved around the usage of Expansion Panels from the Material-Ui-Next librairy.
The solution I found is the following :
Instead of trying to show a Loading component while my list rendered, I reduced the render time of the list by not rendering the ExpansionDetail Component unless the user clicked to expand it.
This way, the list renders well under 0.2 seconds on any devices I've tested. I set the state to collapsed: false on every panel inside the constructor.
class ListItem extends React.Component {
constructor (props) {
super(props);
this.state = {
collapsed: false
};
this.managePanelState = this.managePanelState.bind(this);
}
managePanelState () {
if (this.state.collapsed) {
this.setState({collapsed: false});
} else {
this.setState({collapsed: true});
}
}
Then I use the onChange event of the expansion panel to switch the state between collapsed and not on every ListItemDetail element.
<ExpansionPanel onChange={() => this.managePanelState()}>
I guess sometimes the solution isn't where you had initially planned.
Thanks to everyone who took time to look into my problem!