ReactJS Rendering a component using a function call - javascript

So I am trying to create a component that I can use anywhere in my app. I am not entirely sure how to explain it accurately but I'll use the sweet alert library as an example.
So in swal, you can simply call something like this and the component would render itself.
import swal from 'sweetalert';
swal("Hello world!");
How can I replicate that into my own component so I could call it like
ConfirmationDialog({
message: 'test',
visibility: true,
onConfirm: handleOnConfirm,
onCancel: handleOnCancel
})

It's basically in one of the first points in the react documentation.
State and Lifecycle
At least if I understood your question correctly.
First example on the above website:
const root = ReactDOM.createRoot(document.getElementById('root'));
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);
}
setInterval(tick, 1000);

Related

How do I pass state as a prop? ( undefined error )

app.js:
import './App.css';
import HttpService from '../services/http-service'
import React, { Component } from 'react';
const http = new HttpService()
class App extends Component {
constructor(props){
super(props)
this.state = {accounts: ""}
this.loadData = this.loadData.bind(this)
}
loadData = () => {
http.getAccounts().then(res =>{
this.setState({accounts: res})
console.log(this.accounts)
return res
}, err => {console.log(err)})
}
render() {return (
<div className="container">
<h1 className="title">Retail API</h1>
<DisplayAcc accounts = {this.accounts} />
</div>
)}
}
export default App;
on DisplayAcc, I have a console.log(this.props.accounts) in the constructor.
Output is undefined. What should I do?
I have tried adding these line:
componentDidMount(){
this.loadData = this.loadData.bind(this)
}
Still undefined. Please point out the error or if you have any suggestions/best practices I would highly appreciate that because I'm very new to this. Thanks!
In order to access the accounts state to be passed down from the App component, to your DisplayAcc commponent:
render() {return (
<div className="container">
<h1 className="title">Retail API</h1>
<DisplayAcc accounts = {this.state.accounts} />
</div>
)}
You should be able to access the state by you are trying to reach it by this.accounts
change it too:
this.state.accounts
It's more popular to use so called functional components nowadays in the community. By this approach you wouldnot need the constructor, really recommend reading about this since it will simplify your code quite a bit!
I don't see where loadData is called, which makes me suspicious about the value of accounts.
Consistency is key: if accounts is part of the component's state, then you should access it via this.state.accounts, rather than this.accounts.
Please notice that you console.log meaningfully. Hence, if you want to check this.account, there's no point printing res, as you mentioned in your comment.
First of all, this.loadData = this.loadData.bind(this) is unnecessary because loadData is an arrow function.
Secondly, setState is an async function so you can't read new state just after calling it. So, the following console log will be undefined
this.setState({accounts: res})
console.log(this.accounts) // also this must be this.state.accounts!
Moreover, you are trying to get state value in a wrong way, it must be:
<DisplayAcc accounts={this.state.accounts} />
If you want to read accounts prop in DisplayAcc component, you should add your console log to whether in render or componentDidUpate methods because constructor is only called on first render and your accounts props is empty at that time. But the ones I mentioned are called every time the props are changed.

React Hooks -- Uncaught Invariant Violation: Objects are not valid as a React child

I'm working on the freeCodeCamp drum machine app. In my app with function arrow components, I set state of display with the useState hook in the parent component and pass it as a prop to the child component. In the parent component, I try to render the display state in a div. However, when the method is triggered (on click of the "drum pad" div), the app crashes. In the console I get an error that says "Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {display}). If you meant to render a collection of children, use an array instead."
I've been following along a YouTube tutorial for this project but using arrow function components and Hooks instead of regular classes as used in the tutorial--in the tutorial (around 1:55 of this video) the person successfully does what I'm trying to do, so I think the issue is something to do with using Hooks or arrow function components.
// APP COMPONENT (PARENT)
const sounds = [
{ id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3' },
// etc.
];
const App = () => {
const [display, setDisplay] = useState(''); // <----
const handleDisplay = display => { // <----
setDisplay({ display });
}
return (
<div className="App">
<div className="drum-machine">
<div className="display">
<p>{display}</p> // <---- Related to error in console
</div>
<div className="drum-pads">
{sounds.map(sound => (
<DrumPad
id={sound.id}
letter={sound.letter}
src={sound.src}
handleDisplay={handleDisplay} // <----
/>
))}
</div>
</div>
</div>
);
}
// DRUMPAD COMPONENT (CHILD)
const DrumPad = ({ id, letter, src, handleDisplay }) => {
let audio = React.createRef();
const handleClick = () => {
audio.current.play();
audio.current.currentTime = 0;
handleDisplay(id); // <----
}
return (
<div
className="drum-pad"
id={id}
onClick={handleClick}
>
<p className="letter">{letter}</p>
<audio
ref={audio}
id={letter}
src={src}
>
</audio>
</div>
);
}
You're setting the state as an object instead of a string. Remove the curly brackets around it.
const handleDisplay = display => {
setDisplay(display);
}
This was already answered, but since you are following a tutorial, I am assuming you are learning React and wanted to point a couple of things to help you :)
The incorrect use of state was pointed out, but just for clarification (and the reason I think you were using an object): in the "old" way, with Class components, the state used to be an object, and you needed to update it like an object. This example here shows that. With Hooks, you don't need to set the whole State object, only that specific state property. More info here.
Another point is, in your CodePen example at least, you were missing the import for useState. You either need to import it like this import { useState } from React or use it like this React.useState, since this is a separate module, not imported by default when you import React.
The last point is, when creating components using a loop (like your <DrumPad> with the map) you need to provide a "key" attribute. that will help React keep track of things that needs to be updated or rerendered.
O updated your code with those changes in this link, if you wanna see it working:
https://codesandbox.io/s/reverent-browser-zkum2
Good luck and hope you are enjoying React Hooks :)

Understanding HOCs with Redux

I have a users component that just displays a list of users. I have tried to wrap it in a HOC loading component so that it only displays once the users are loaded, otherwise shows a loading spinner (well just text for now)
this is my HOC:
const Loading = (propName) => (WrappedComponent) => {
return class extends React.Component{
render(){
return this.props[propName].length === 0 ? <div> Loading... </div> : <WrappedComponent {...this.props} />
}
}
}
export default Loading;
at the bottom of my users component I have this:
export default connect(
mapStateToProps,
mapDispatchToProps
)(Loading('users')(Users));
currently, the word Loading... is just staying on screen. and propName is coming through as undefined
I think for some reason the users component is never getting populated. what have i done wrong?
Update after comments
My answer below is a misleading one since I hadn't understood your intention properly at that time. Also, my explanation about not getting props is somehow wrong. It is true if we don't render the components but here you are doing it. So, the problem was not that.
The problem here is your Loading component isn't rendered again after fetching users. Actually, you never fetch the users :) Here are the steps of your app (probably).
You are exporting a HOC function, not the Wrapped one here. It comes from your Users file but it does not export the real Users component. This is important.
Your parent renders the first time and it renders the exported HOC component.
Your child component renders and fall into Loading one not the Users one.
In Loading your users prop is empty, so you see Loading....
Your Users component never renders again. So, fetching the users there don't update the state.
Your solution is extracting the fetch out of Users and feed this component. Probably in a parent one. So:
Parent fetches the users then renders itself and all its children.
Your Loading HOC component renders a second time.
I don't know how do you plan to use this HOC but if I understood right (since I'm not so experienced with HOC) in your case the problem is you are not passing any prop to the Loading function. This is because you are not using it as a regular component here. It is a function and propName here is just an argument.
When we render a stateless function like this:
<Loading propName="foo" />
then there will be a props argument for our function. If we don't render it like that there will be no props argument and no props.propName. If this is wrong please somebody fix this and explain the right logic. So, you want to do something like this probably:
class App extends React.Component {
render() {
return (
<div><FooWithLoading /></div>
);
}
}
const Loading = (users) => (WrappedComponent) => {
return class extends React.Component {
render() {
return (
users.length === 0 ? <div> Loading... </div> :
<WrappedComponent
users={users}
/>
);
}
}
};
const Foo = props => {
return (
<div>
Users: {props.users}
</div>
);
}
const FooWithLoading = Loading("foobar")(Foo);
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>
So in your case:
export default connect(
mapStateToProps,
mapDispatchToProps
)(Loading('users')(Users));
should work?
Or you need to render your component properly in a suitable place of your app.

Passing function down multiple children reactJS

I'm trying to build an application to help me learn react. It's a simple app that takes users input location, gets the coordinates from googles geolocation api and then gets the weather conditions for that location with dark skys api.
I originally had all my application logic in one container which you can see here https://github.com/darragh3277/night-sky
I want to separate out my logic a bit more to as I felt my one container was doing too many things. I'm looking to structure my app as follows.
App.js - Holds state, locationChangeHandler function to pass down to LocationSearchContainer.
LocationSearchContainer - Geolocation API and calls LocationSearch to display search bar
WeatherContainer - WeatherAPI and calls Weather component to render the display.
I believe this gives me a better separation of concerns and makes my code a bit more readable.
Because of this structure I think I need a locationChangeHandler in my App.js that will be passed to my Dump LocationSearch component. This tightly couples my App.js to the search bar but as that's the core function of the app I don't believe it's a problem.
What I'm having trouble with is how to pass my handler from App.js -> LocationSearchContainer -> LocationSearch. Here is my poor attempt so far:
In App.js I have
handleLocationChange = e => {
e.preventDefault();
console.log('hello');
//Calls getLocation in LocationSearchContainer and updates state
}
render(){
return (
<LocationSearchContainer onLocationChange={this.handleLocationChange} />
)
}
In LocationSearchContainer I have the following:
import React from 'react';
import LocationSearch from '../components/LocationSearch';
class LocationSearchContainer extends React.Component{
getLocation = (address) => {
//DO STUFF
}
render(){
return (
<LocationSearch onLocationChange={this.props.handleLocationChange} />
)
}
}
export default LocationSearchContainer;
And finally LocationSearch:
import React from 'react';
const LocationSearch = (
<form onSubmit={props.onLocationChange}>
<div className="input-group">
<input type="text" name="location" className="form-control" placeholder="Search location..." />
<button className="btn btn-primary">Go!</button>
</div>
</form>
)
export default LocationSearch;
How can I pass this handler down correctly?
Once I have the above working I'll also need to call the getLocation in my SearchLocationContainer from App.js but I'm not sure how to do that either?
EDIT
I've figure out part one of my problem. I was calling the function by name rather than the prop name I was passing. So in the render() function of my LocationSearchContainer I should have had
return (
<LocationSearch onLocationChange={this.props.handleLocationChange} />
)
rather than my original.
That still leaves me with the problem of calling my LocationSearchContainer's getCoordinates function from my App.js file. Anyone able to help me with that?
You are passing your handler like that:
<LocationSearchContainer onLocationChange={this.handleLocationChange} />
So, in LocationSearchContainer component, your handler function's name is onLocationChange, within props it is this.props.onLocationChange.
So, you need to pass this function to your LocationSearch component like that:
<LocationSearch onLocationChange={this.props.onLocationChange} />
Lastly, you need to use it in LocationSearch as props.onLocationChange
For your second question, you shouldn't try to invoke a child component's method from the parent. Change your logic. Either keep this function in the parent, then pass it again as a prop to your child component or move the logic to your child component.

#react-starter-kit React onClick not firing on page

I'm using the React-Starter-Kit and am having an issue with an onClick={this.handleClick} not firing in the browser.
What is happening: The Colorswatches.js component is loading and showing up in the browser but the onClick isn't working. No console logs are showing up.
What I think is the problem: Rendering everything server side and passing to client, client gets static react html with no event bindings.
How do I get the click event to work client side after server side rendering?
EDIT: Updating code with provided example from jgldev
EDIT 2: Added componentDidMount() function. With a console log, still not seeing the log on page load
EDIT 3: My issued was with another part of the React-starter-kit that was bombing out the client side re-render. I marked the first answer as correct.
src/component/ColorSwatches/ColorSwatches.js:
import React, { Component, PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './ColorSwatches.scss';
class ColorSwatches extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
console.log('I was mounted');
}
handleClick(){
console.log('I was clicked');
}
render(){
let colorSlices = this.props.colorSlices;
let sku = this.props.sku;
let currentSkuIndex = 0
return (
<div className='pdpColors'>
<div className='colorSwatches' >
{ colorSlices.map((colorSlice, index) => {
return (
<div title={colorSlice.color} onClick={()=>this.handleClick()} key={index}>
<img src={colorSlice.swatchURL}/>
</div>
)
})}
</div>
</div>
)
}
}
export default withStyles(ColorSwatches,s);
My guess is that the following is going on:
Originally, this referred to the mounted DOM component, which is not what you want.
Wrapping the onClick to onClick={()=>this.handleClick()} is a step in the right direction, but not enough. this now refers to a react component, but not the right one. It is defined inside a .map function, so it refers to the colorSlice. Which is not what you want.
My advice take it one step further:
inside render, before your return statement, add the following line
let that = this; // this refers to ColorSwatches component, as intended
and inside the mapping function change onClick to:
onClick={that.handleClick} // no need to add the wrapper now
Hope this helps.
maybe try this,
first of all make a var that contains this. this should happen in your render()
var self = this;
Then on your onClick
onClick={self.handleClick.bind(this)}
This worked for me when i ran into this problem.
Hope it works out!
on the constructor:
this.handleClick = this.handleClick.bind(this)
Then, in the render function:
onClick={()=>this.handleClick()}

Categories