I'm building my first React app. I'm trying to render some Routes from react-router-dom.
From the main component I call to my api to get a json object, then I update the state. The problem is my child component doesn't re-render after I have set the new state so I don't have props in the child components. I have used some functions like forcerender and componentWillReceiveProps but still doesn't work
I'm sure it's not a big problem but I have been trying to fix this for a couple of hours and I haven't been able to make it work.
Here is my latest attempt:
class DetectorEfficiencyCalculator extends Component {
constructor(props) {
super(props);
this.state = {
detectors: []
};
axios.get(`/detectors`)
.then(res => {
const detectors = res.data;
this.setState({ detectors });
console.log('state updated')
});
}
render() {
return (
<div className="DetectorEfficiencyCalculator">
<RoutesHandler detectors={this.state.detectors}/>
</div>
);
}
}
class RoutesHandler extends Component{
constructor(props) {
super(props);
this.state = { detectors: props.detectors } ;
}
componentWillReceiveProps(nextProps) {
this.setState({detectors:nextProps.detectors})
this.forceUpdate()
}
render() {
console.log('render')
return (
<div className="RoutesHandler">
<Switch>
<Route exact path='/frontend/Detectors' component={DetectorList} detectors={this.props.detectors}/>
<Route path='/frontend/Detectors/:number' component={DetectorDetail} detectors={this.props.detectors}/>
</Switch>
</div>
);
}
}
class DetectorList extends Component {
render () {
console.log('renderList')
if (!this.props.detectors) {
return null;
}
return (
<ul>
{this.props.detectors.map(u => {
return (
<Detector
id={u.id}
name={u.name}
single={u.single}
threshold={u.threshold}
angle={u.angle}
/>
);
})}
</ul>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Thanks in advance for your help :)
Try something like this:
class DetectorEfficiencyCalculator extends Component {
state={detectors:[]}
componentDidMount(){
const self = this;
axios.get(`/detectors`) //do it here...
.then(res => {
const detectors = res.data;
self.setState({ detectors });
console.log('state updated')
});
}
render() {
return (
<div className="DetectorEfficiencyCalculator">
<RoutesHandler detectors={this.state.detectors}/>
</div>
);
}
}
class RoutesHandler extends Component{
render() {
console.log('render')
return (
<div className="RoutesHandler">
<Switch>
<Route exact path='/frontend/Detectors' component={DetectorList} detectors={this.props.detectors}/>
<Route path='/frontend/Detectors/:number' component={DetectorDetail} detectors={this.props.detectors}/>
</Switch>
</div>
);
}
}
class DetectorList extends Component {
render () {
console.log('renderList')
if (!this.props.detectors) {
return null;
}
return (
<ul>
{this.props.detectors.map(u => {
return (
<Detector
id={u.id}
name={u.name}
single={u.single}
threshold={u.threshold}
angle={u.angle}
/>
);
})}
</ul>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Basically, you dont want to do any ajax calls or db access in the constructor, this is not the right way to do it in React since the constructor can be called multiple times. Instead use the React component lifecycle method componentDidMount to initiate the api call. In addition I used a variable (self) to hold a reference to the component (this) so I can use it in the axios promise handler.
Ok, I got into the solution: The route renders a function in which I render the component and load it with the props I need.
this.RenderDetectorDetail = (props) => {
return (
<DetectorDetail detectors={this.props.detectors}/>
);
};
<Route exact path='/frontend/Detectors' render={this.RenderDetectorList} />
Related
Expecting effect: click <li> --> take index --> send this index to component Watch.
When I click <li>, I grab the index and move it to theWatch component. However, when I click the second li it returns the index of the one I clicked for the first time. I think this is because it updates this index via componentDidMount. How can I reference this index after componentDidMount?
Todo
class Todo extends Component {
render () {
return (
<div className = "itemTodos" onClick={()=> this.props.selectTodo(this.props.index)}>
</div>
)
}
}
export default Todo;
App
class App extends Component {
constructor(){
super();
this.state {
selectedTodoIndex: index
}
}
selectTodo = (index) => {
this.setState({
selectedTodoIndex: index
})
}
render () {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
selectTodo ={this.selectTodo}
/>
)
}
</ul>
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
</div>
)
}
}
export default App;
Watch
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
First of all you you use selectedTodoIndex in
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
but it not specified in your render code. Add
const {selectedTodoIndex} = this.state;
in render function.
Second, use componentDidUpdate in Watch for update inner state on props update:
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
componentDidUpdate (prevProps) {
if (prevProps.selectedTodo !== this.props.selectedTodo)
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
If i am not wrong your Todo component is in watch??. So Watch component should be like this :
render () {
return (
<div>
<Todo index={this.state.selectedIndex} selectedTodo={this.props.selectedTodoIndex}/>
</div>
)
}
Here i made codesandbox of this code . Feel free to checkout and let me know if you any doubt. Code link : https://codesandbox.io/s/frosty-chaplygin-ws1zz
There are lot of improvements to be made. But I believe what you are looking for is getDerivedStateFromProps lifeCycle method in Watch Component. So the code will be:
getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.selectedTodoIndex !== prevState.selectedTodoIndex) {
return { selectIndex: nextProps.selectedTodoIndex }
}
}
This will check if the selected index has changed in App Component, if yes it will update the state in Watch Component.
I want to create a React HOC that would ideally receive two components instead of one wrapped component and toggle between them. That is, in the code below, instead of <h3>component one</h3> and <h3>component two<h3>, they would each represent child components. How would I be able to accomplish this? Some psuedo code for how I would write this HOC:
<HOC>
<ComponentOne />
<ComponentTwo />
</HOC>
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
hoc(componentOne, componentTwo)
class HOC extends React.Component {
constructor() {
super();
this.state = {
onClick: false,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({onClick: !this.state.onClick});
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
<h3>component one</h3> :
<h3>component two</h3>
}
</div>
);
}
}
ReactDOM.render(<HOC />, app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I am not sure if I understood you. Why do you need it to be HOC?
If you would pass components as props like that:
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
Then you would be able to access them using props.
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
this.props.componentOne :
this.props.componentTwo
}
</div>
);
}
If a component has more than one child then this.props.children will be an array.
class HOC extends React.Component {
// ... rest of code ....
render() {
const { onClick } = this.state;
const { children } = this.props;
return !onClick ? children[0] : children[1];
}
}
Then use it like so:
<HOC>
<div>Child One</div>
<div>Child Two</div>
</HOC>
Obviously this will only work with two children but you could extend it by passing an integer to <HOC> through props to tell it what child to select.
Edit
After a quick look at the docs this is a better version of what I wrote above as this.props.children is not an array, it is an opaque data structure:
class HOC extends React.Component {
// ... rest of code ...
render() {
const { onClick } = this.state;
const children = React.Children.toArray(this.props.children);
return !onClick ? children[0] : children[1];
}
}
I've been working on learning React to see if it suits my organization's needs, so needless to say I'm new at it. I've got a sample app that I've been working on to see how it works. I've gone through several of the answers here and haven't found one that fixes my problem.
I'm running into the problem where I get a "Uncaught (in promise) TypeError: Cannot read property 'params' of undefined" in the "componentDidMount()" at "const { match: { params } } = this.props;" method in the component below. I have a very similar component that takes an id from the url, using the same method, and it works fine. I'm confused as to why one is working and another isn't. I'm probably just making a rookie mistake somewhere (perhaps more than one), any hints/answers are appreciated.
The routing:
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route path='/' component={BaseView} />
<Route path='/test' component={NameForm} />
<Route path='/home' component={Home} />
<Route path='/quizzes' component={ViewQuizzes} />
<Route path='/comment/:rank' component={GetCommentsId} /*The one that works*//>
<Route path='/comment/edit/:testid' component={GetCommentEdit} /*The one I'm having trouble with*//>
<Route path='/comments' component={GetCommentsAll} />
</div>
</BrowserRouter>
);
}
}
The working component:
class GetCommentsId extends Component{
constructor (props) {
super(props)
this.state = {
Comments: [],
output: "",
wasClicked: false,
currentComment: " ",
}
this.handleCommentChange = this.handleCommentChange.bind(this);
}
componentDidMount(){
const { match: { params } } = this.props;
const url = 'http://localhost:51295/api/Values/' + params.rank;
axios.get(url).then(res => {
const comments = res.data;
this.setState({ comments });
this.output = (
<div>
<ul>
{ this.state.comments.map
(
comment =>
(<Comment
QuizId = {comment.Rank}
FirstName = {comment.FirstName}
Comments = {comment.Comments}
TestId = {comment.TestimonialId}
/>)
)}
</ul>
</div>
);
//console.log("From did mount: " + this.currentComment);
this.forceUpdate();
});
}
componentDidUpdate(){}
handleCommentChange(event){
//console.log("handle Comment Change activated");
}
handleClick(comment){
this.wasClicked = true;
this.currentComment = comment.Comments;
console.log(comment.Comments);
this.forceUpdate();
}
render () {
if(this.output != null){
if(!this.wasClicked){
return (this.output);
}
else{
console.log("this is the current comment: " + this.currentComment);
return(
<div>
{this.output}
<NameForm value={this.currentComment}/>
</div>
);
}
}
return ("loading");
}
}
The one that isn't working:
class GetCommentEdit extends Component {
constructor (props) {
super(props)
this.state = {
Comments: [],
output: "",
match: props.match
}
}
componentDidMount(){
const { match: { params } } = this.props;
const url = 'http://localhost:51295/api/Values/' + params.testid;
axios.get(url).then(res => {
const comments = res.data;
this.setState({ comments });
this.output = (
<div>
<ul>
{ this.state.comments.map
(comment =>
(<EditComment
QuizId = {comment.Rank}
FirstName = {comment.FirstName}
Comments = {comment.Comments}
TestId = {comment.TestimonialId}
/>)
)}
</ul>
</div>
);
//console.log("From did mount: " + this.currentComment);
this.forceUpdate();
});
}
render(){
return(
<div>
{this.output}
</div>
);
}
}
I've created a small app for you to demonstrate how to implement working react router v4.
On each route there is a dump of props, as you can see the params are visible there.
In your code I don't see why you are not using Switch from react-router v4, also your routes don't have exact flag/prop. This way you will not render your component views one after another.
Link to sandbox: https://codesandbox.io/s/5y9310y0zn
Please note that it is recommended to wrap withRouter around App component, App component should not contain <BrowserRouter>.
Reviewing your code
Please note that updating state triggers new render of your component.
Instead of using this.forceUpdate() which is not needed here, update your state with values you get from resolving the Promise/axios request.
// Bad implementation of componentDidMount
// Just remove it
this.output = (
<div>
<ul>
{this.state.comments.map
(
comment =>
(<Comment
QuizId={comment.Rank}
FirstName={comment.FirstName}
Comments={comment.Comments}
TestId={comment.TestimonialId}
/>)
)}
</ul>
</div>
);
//console.log("From did mount: " + this.currentComment);
this.forceUpdate();
Move loop function inside render method or any other helper method, here is code for using helper method.
renderComments() {
const { comments } = this.state;
// Just check if we have any comments
// Return message or just return null
if (!comments.length) return <div>No comments</div>;
// Move li inside loop
return (
<ul>
{comments.map(comment => (
<li key={comment.id}>
<Comment yourProps={'yourProps'} />
</li>
))}
</ul>
)
};
Add something like isLoading in your initial state. Toggle isLoading state each time you are done with fetching or you begin to fetch.
this.setState({ isLoading: true }); // or false
// Initial state or implement in constructor
state = { isLoading: true };
Render method will show us loading each time we are loading something, renderComments() will return us comments. We get clean and readable code.
render() {
if (isLoading) {
return <div>Loading...</div>
}
return (
<div>
{this.renderComments()}
</div>
);
}
I'm trying to understand how the code below, which is from Redux examples TODOMVC, can be written using the class notation.
The code is
const App = ({todos, actions}) => (
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
I tried the following but it doesn't work, I get Warning: App(...): When calling super() inApp, make sure to pass up the same props that your component's constructor was passed.
class App extends React.Component {
constructor({todos, actions}) {
super({todos, actions});
this.todos = todos;
this.actions = actions;
}
render() {
return(
<div>
<Header addTodo={this.actions.addTodo} />
<MainSection todos={this.todos} actions={this.actions} />
</div>
)
}
}
Whatever is passed to App is props. And ({ todos, actions }) is just destructuring from props. This should work:
class App extends React.Component {
render() {
const { todos, actions } = this.props;
return(
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
)
}
}
By setting this.todo = todos in constructor, you're setting an instance level property. Which means if the props changes later, Header and MainSection will not be updated.
You can simply do what React asks, pass the whole props to the superclass and get out the properties you want explicitly
class App extends React.Component {
constructor(props) {
super(props);
this.todos = props.todos;
this.actions = props.actions;
}
render() {
return(
<div>
<h1>Actions: {this.actions}</h1>
{/*<Header addTodo={this.actions.addTodo} />
<MainSection todos={this.todos} actions={this.actions} />*/}
</div>
)
}
}
ReactDOM.render(<App todos={[]} actions={'some action'} />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
I have a React component MoviesGallery.js with the following configuration:
class MoviesGallery extends Component {
constructor(props) {
super(props)
this.state = { currentImage: 0 };
this.closeLightbox = this.closeLightbox.bind(this);
this.openLightbox = this.openLightbox.bind(this);
this.gotoNext = this.gotoNext.bind(this);
this.gotoPrevious = this.gotoPrevious.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({movies_genre: nextProps.movies_genre})
}
I have rendered the component in my main App.js file like so:
class App extends Component {
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
<RaisedButton primary={true} label="Query" className="header_buttons"/>
<RaisedButton secondary={true} label="Reset" className="header_buttons"/>
</header>
<MoviesGallery/>
</div>
</MuiThemeProvider>
);
}
}
I want to update the props of my MoviesGallery component without recreating the component. Since I already added the componentWillReceiveProps() to MoviesGallery component, how can I make it so when 'Query' button is clicked, it will pass new props to the already rendered MoviesGallery and componentWillReceiveProps() should cause it to re-render since the state will change.
Just confused about the function that will change the props themselves on-click of the rendered MoviesGallery component.
Thanks in advance!
When a parent pass a new (value) prop to the child, the child component will call the render method automatically. There is no need to set a local state inside the child component to "store" the new prop.
Here is a small example of a Counter that receives a count prop and just displays it, while the parent App in this case will change the value in its state and pass the new value to Counter:
class Counter extends React.Component {
render() {
const { count } = this.props;
return (
<div>{count}</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
onClick = () => {
this.setState({ count: this.state.count + 1 });
}
render() {
const { count } = this.state;
return (
<div>
<Counter count={count} />
<button onClick={this.onClick}>Add to counter</button>
</div>);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
you can use the 'state' for your MovieGallery.js props because the state is an object that changes and you must your code like below :
class App extends Component {
state = {
query : null
}
myFunction(query){
this.setState({query});
}
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to React</h1>
<RaisedButton primary={true} label="Query" className="header_buttons" onClick={this.myFunction = this.myfunction.bind(this)}/>
<RaisedButton secondary={true} label="Reset" className="header_buttons"/>
</header>
<MoviesGallery newProps = {this.state.query}/>
</div>
</MuiThemeProvider>
);
}
}
i hope it helps