React append component programmatically - javascript

I want to create a react component instance and render it in a static place programmatically.
My use-case is that I open a sequence of dialogs in an unknown length and when I get a response from a dialog I open the next.
I want to do something like:
const DialogExample = () => ({ question, onAnswer }) =>
(<div>
{question}
<button onClick={onAnswer}>answer</button>
</div>);
class SomeComponent extends React.Component {
async start() {
const questions = await getSomeDynamicQuestions();
this.ask(questions);
}
ask(questions) {
if (questions.length === 0) {
// DONE.. (do something here)
return;
}
const current = questions.pop();
React.magicMethod(
// The component I want to append:
<DialogExample
question={current}
onAnswer={() => this.ask(questions)}
/>,
// Where I want to append it:
document.getElementsByTagName('body')[0]);
}
render() {
return (
<div>
<button onClick={this.start}>start</button>
</div>);
}
}
I know that's not very "react-like", and I guess the "right" way of doing it will be storing those questions in state and iterate over them in "someComponent" (or other) render function, but still, I think that this pattern can make sense in my specific need.

Sounds like a case for Portals. I'd recommend doing something like this:
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.body = document.getElementsByTagName('body')[0];
this.state = {
questions: [],
}
}
async start() {
const questions = await getSomeDynamicQuestions();
this.setState({ questions });
}
nextQuestion() {
this.setState(oldState => {
const [first, ...rest] = oldState.questions;
return { questions: rest };
})
}
render() {
const { questions } = this.state;
return (
<div>
<button onClick={this.start}>start</button>
{questions.length > 0 && ReactDOM.createPortal(
<DialogExample
question={questions[0]}
onAnswer={() => this.nextQuestion()}
/>,
this.body,
)}
</div>
);
}
}

Related

why the function didn't return data in component although it appear on console correctly?

I have to show books that user had added them before and this will happen when the user clicks button but I didn't get a result, what is the problem?
//here is the function
displayBooks = () => {
const { users, books } = this.state
const user = users.find(user => user.email === currentUser.email)
const arrayHaveFavBooksId = user.favBooks
let arrayHaveObjOfuserFavBooks = [];
for (let book of books) {
for (let bookId of arrayHaveFavBooksId)
if (bookId === book.id) {
arrayHaveObjOfuserFavBooks.push(book)
}
}
//
console.log(arrayHaveObjOfuserFavBooks);
const showBooks = arrayHaveObjOfuserFavBooks.map((book, id) => {
return (
<figure key={id}>
<img src={book.photoURL} alt={book.title} />
</figure>
)
})
return showBooks
}
here is the render on component
render() {
return (
<section className="profile">
<button onClick={this.displayBooks}> show my Books</button>
</section>
);
}
// here is imgfor logging
You're confusing the onClick callback with the rendering phase from the Component. You're not supposed to return itens inside the onClick callback, but rather trigger a re-render by calling setState (or other re-rendering operation e.g. update on Redux).
One way to solve this would be to create a flag on the component state and update it from the onClick callback. like so:
class Comp extends React.Component {
state = {
books: ...,
users: ...,
booksVisible: false,
}
// displayBooks implementation here
toggleVisibility = () => {
const { booksVisible } = this.state
// Toggles it so you can open and close
this.setState({ booksVisible: !booksVisible })
}
render() {
return (
<section className="profile">
<button onClick={this.toggleVisibility}> Show my books</button>
{ booksVisible ? this.displayBooks() : null }
</section>
);
}
}
In this way, the onClick callback manages only the state of the component, while the rendering is entirely made inside of the render function.

React issue with rendering data from firebase

Basically I have an issue with rendering information got from firebase to the screen.
When I'm trying to call the function which gets the information from the database inside componentDidMount(), the function is not even executed, but when I call it inside the render() function, which I know it's now the right thing to do it works, it goes into an infinite loop and it keeps accessing the database over and over again, but it renders the correct information to the screen. So the function itself is not the issue, I guess, since it is able to retrieve the information from the database.
Also a console.log() inside the componentDidMount() seems to work so componentDidMount() does fire.
So how should I go forward with this issue? I've been struggling with this for several hours now. I can't seem to find the issue.
This is my code:
export default class Cars extends Component {
constructor(){
super();
this.state = {
cars: []
}
}
componentDidMount(){
this.loadCarsFromDB();
}
loadCarsFromDB = () => (
<FirebaseContext.Consumer>
{firebase => {
firebase.accessFirebase("cars").get()
.then(snapshot => {
let cars = [];
snapshot.docs.forEach(doc => {
cars.push(doc.data());
})
return cars;
})
.then(cars => {
this.setState({cars: cars});
})
.catch(err => {
console.log(err);
});
}
}
</FirebaseContext.Consumer>
)
renderCars = () => {
return this.state.cars.map(car => <Car
brandName={car.brandName}
model={car.model}
color={car.color}
price={car.price} />)
}
render() {
return (
<div className="car-item">
{this.renderCars()}
</div>
);
}
}
Firebase class except the credentials
export default class Firebase {
constructor() {
app.initializeApp(config);
}
accessFirebase = () => {
let db = app.firestore();
return db.collection("cars");
}
}
This is the Car function
const car = (props) => (
<div className="Car">
<span>{props.brandName ? props.brandName : "Nu exista"}</span>
<span>{props.model ? props.model : "Nu exista"}</span>
<span>{props.color ? props.color : "Nu exista"}</span>
<span>{props.price ? props.price : "Nu exista"}</span>
</div>
)
export default car;
And this is the index.js file. I don't know, maybe it has something to do with the use of contexts. I basically create only one firebase instance which should allow me to query the database from anywhere in the code by using only this very instance.
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
App.jsx file
class App extends Component {
render(){
return(
<div>
<Cars/>
</div>
)
}
}
export default App;
You are not supposed to use the FirebaseContext.Consumer component from loadCarsFromDB. So I would lift up FirebaseContext.Consumer around Cars and pass down the firebase property as a prop.
class App extends Component {
render(){
return(
<div>
<FirebaseContext.Consumer>
{firebase => (
<Cars firebase={firebase}/>
)
}
<FirebaseContext.Consumer />
</div>
)
}
}
loadCarsFromDB = () => (
this.props.firebase.accessFirebase("cars").get()
.then(snapshot => {
let cars = [];
snapshot.docs.forEach(doc => {
cars.push(doc.data());
})
return cars;
})
.then(cars => {
this.setState({cars: cars});
})
.catch(err => {
console.log(err);
});
)

Access variable returned from react function within render (in loop)

I have a react class component, where I am passing list of module names and some other attributes. I tried to obtain module code by calling a function getModuleCode() and passing module name as a parameter.
class ModulesListing extends React.Component {
getModuleCode(module){
var moduleCode = 0;
// Do relevant calls and get moduleCode
return moduleCode;
}
render(){
var {modulesList} = this.props;
modulesList.forEach(module => {
//here I need to call getModuleCode as getModuleCode(module.name)
var moduleCode = getModuleCode(module.name)
console.log(moduleCode)
})
return (
//Info to display
);
}
}
If I tried as above mentioned, it prints as undefined
Also tried with setting as state, which is not suitable for looping. Here what I wanted is to get module code wrt certain module.
You could get the codes in componentDidMount and in componentDidUpdate if the modulesList change, and store them in state.
Example
function doCall() {
return new Promise(resolve =>
setTimeout(() => resolve(Math.random().toString()), 1000)
);
}
class ModulesListing extends React.Component {
state = { codes: [] };
componentDidMount() {
this.getModuleCodes();
}
componentDidUpdate(prevProps) {
if (this.props.modulesList !== prevProps.modulesList) {
this.setState({ codes: [] }, this.getModuleCodes);
}
}
getModuleCodes = () => {
Promise.all(this.props.modulesList.map(doCall)).then(codes => {
this.setState({ codes });
});
};
render() {
const { modulesList } = this.props;
const { codes } = this.state;
if (codes.length === 0) {
return null;
}
return (
<div>
{modulesList.map((module, index) => (
<div>
{module}: {codes[index]}
</div>
))}
</div>
);
}
}
ReactDOM.render(
<ModulesListing modulesList={["a", "b", "c"]} />,
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>

Handling event on array item won't work - ReactJS

I try to map an array and put click event on the array items. I know it's a bit different because of how JavaScript handles functions but I can't make it work. I get the error: Cannot read property 'saveInStorage' of undefined. Can anyone tell me what I'm doing wrong? Thanks in advance! Here is my code:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
this is undefined in renderUser()
You need to bind this for renderUser() in your constructor.
Also, you are calling saveInStorage() every time the component is rendered, not just onClick, so you'll need to use an arrow function in renderUser
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this);
this.renderUser = this.renderUser.bind(this);
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
Instead of binding you can also use an arrow function (per mersocarlin's answer). The only reason an arrow function will also work is because "An arrow function does not have its own this; the this value of the enclosing execution context is used" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The enclosing execution in your case is your render, where this is defined.
You need to make two changes to your code which are outlined below.
You are invoking the function when the component is rendered. To fix this update this line to the following
<p key={i} onClick={() => this.saveInStorage(user)}>
This means that the function will only be invoked when you click on the item.
You also need to bind the renderUser in your constructor or else use an arrow function.
this.renderUser = this.renderUser.bind(this);
See working example here.
Your onClick event handler is wrong.
Simply change it to:
onClick={() => this.saveInStorage(user)}
Don't forget to also bind renderUser in your constructor.
Alternatively, you can choose arrow function approach as they work the same as with bind:
class Gebruikers extends React.Component {
constructor(props) {
super(props)
this.state = {
users: [{ id: 1, name: 'user1' }, { id: 2, name: 'user2' }],
}
}
saveInStorage = (e) => {
alert("test")
}
renderUser = (user, i) => {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
{user.name}
</p>
)
}
render() {
return (
<div>{this.state.users.map(this.renderUser)}</div>
);
}
}
ReactDOM.render(
<Gebruikers />,
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>
Paul Fitzgeralds answer is the correct one, although I'd like to propose a different way of handling this, without all the binding issues.
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
}
saveInStorage = (e) => {
console.log("test");
};
render() {
return (
<div>
{this.state.users.map((user, i) => {
return (<p key={i} onClick={() => this.saveInStorage(user)}>f</p>);
})}
</div>
);
}
}
With saveInStorage = (e) => {}; you are binding the saveInStorage function to the this context of your class. When invoking saveInStorage you'll always have the (at least I guess so in this case) desired this context.
The renderUser function is basically redundant. If you return one line of JSX, you can easily do this inside your render function. I think it improves readability, since all your JSX is in one function.
You are not sending the parameters to this.renderUser
this.state.users.map((user, i) => this.renderUser(user, i))
Also your onClick function should be slightly changed. Here's the full code changed:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map((user, i) => this.renderUser(user, i))
}
</div>
);
}
}

React Intercept component unmounting (Functional and Class Components)

I need to always intercept when React unmounts a Component, no matter if that is a Functional or Class based component.
Here is my case:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
class Comp extends React.Component {
render() {
return (<h1>Hello World</h1>);
}
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
Now, as you can easily see, I am able to hook on every time <Comp /> gets unmounted. That is just what I need.
Things will dramatically change when that <Comp /> is a functional component:
function observe(component) {
const p = component.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return component;
}
function Comp() {
return (<h1>Hello World</h1>);
}
class App extends React.Component {
render() {
const active = this.state && this.state.active;
const toggle = () => this.setState({
active: !active,
});
return (
<div>
<button onClick={toggle}>Toggle</button>
<hr />
{active && observe(<Comp />)}
</div>
);
}
}
So, my question is:
How can I hook on functional components?
I can change approach (or use React internal Apis), I just need to always intercept changes on a component passed as arguments for observe.
You can't. Functional components don't have lifecycles (yet).
Instead of messing with the functional component directly, why don't you just wrap the functional component in a class with a HOC. You could use recompose toClass for this.
function observe(component) => {
const classComponent = toClass(component):
const p = classComponent.type.prototype;
const delegate = p.componentWillUnmount || function noop() {};
if(!delegate.__decorated) {
p.componentWillUnmount = function() {
console.log('I am going to be unmounted');
return delegate.apply(this, arguments);
}
p.componentWillUnmount.__decorated = true;
}
return classComponent;
}
Or just copy the code from here.

Categories