ReactDOM.createPortal() renders empty element when called in a function - javascript

I have a simple React.js app that tries to render content in a Portal on a callback, does someone have any ideas why it isn't rendering anything?
Runnable CodeSandbox
import React from 'react'
import ReactDOM from 'react-dom'
import './App.css';
function App() {
const showElement = React.useCallback(() => {
const element = document.createElement('div');
document.querySelector('body').appendChild(element)
ReactDOM.createPortal(() => <div>TEST</div>, element)
}, [])
return (
<div className="App">
<button onClick={showElement}>Click to Render Portal</button>
</div>
);
}
export default App;

You aren't returning the created portal in the return of the App component, so it's not actually being rendered.
Try something more like:
function App() {
const elementRef = useRef(document.createElement("div"));
const showElement = React.useCallback(() => {
document.querySelector("body").appendChild(elementRef.current);
}, []);
return (
<div className="App">
<button onClick={showElement}>Click to Render Portal</button>
{ReactDOM.createPortal(<div>TEST</div>, elementRef.current)}
</div>
);
}
This creates a React ref to hold the created DOM element, renders the portal with the return of the component JSX, and appends the DOMNode in the button's onClick handler.

Related

React TypeError: "x" is not a function

I called a function setTodos from the parent in my child components, but this returns the following error:
setTodos is not a function
Can you explain me why this happened, thanks a lot. Here is my code:
import React, { useState } from 'react';
import './App.css';
import Form from './components/Form';
import TodoList from './components/TodoList';
function App() {
const [inputText, setInputText] = useState("");
const [todos, setTodos] = useState([]);
return (
<div className="App">
<header>
<h1>Phuc's Todo list</h1>
</header>
<Form inputText={inputText} todos={todos} setTodos={setTodos}/>
<TodoList/>
</div>
);
}
export default App;
import React from 'react';
const Form = ({inputText, setInputText, todos, setToDos}) => {
const inputTextHandler = (e) => {
setInputText(e.target.value);
}
const submitTodoHandler = (e) => {
e.preventDefault();
setToDos([
...todos,
{text: inputText, completed: false, id: Math.randowm()*1000}
])
}
return (
...
)
}
There is a typo in your code while calling the setTodos in child component
It should be setTodos in child instead of setToDos. You have capital D, It should be small d.
As Javascript is case sensitve langauge. So you have to use the exact term.
setTodos([//here your code]);
In your parent component, setTodos need to be a callback if you want to do it that way.
Try with setTodos={(newTodos) => setTodos(newTodos)} inside your of parent component.
You are passing the setTodos as a prop into your child component. So, probably you are calling there setToDos instead setTodos.

How to implement API search onClick

My project is using React, Axios, and movieDB's API. I am trying to make it so that when I type out a movie title and either hit enter and/or click submit then the API will return the title of the movie searched for as an h1 element.
currently, the code works as soon as I refresh the browser and the only way for it to function properly is if I go into the code and replace ${query} with what I want to search for, ie joker. I have tried adding the onclick to the button : <button onclick="componentDidMount()">submit</button>. This did not do anything, however.
App.js
import React from "react"
import Movielist from './components/Movielist'
function App() {
return (
<div>
<input type="search" />
<button onclick="componentDidMount()">submit</button>
<h1 id="title">title</h1>
<Movielist />
</div>
)
}
export default App
Movielist.js
import React from 'react';
import axios from 'axios';
export default class Movielist extends React.Component {
state = {
title: ""
}
componentDidMount() {
const API_KEY = '*****************';
const query = document.getElementById('search');
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
const title = res.data['results'][0]['title'];
this.setState({ title });
})
}
render() {
return (
<h1>{this.state.title}</h1>
)
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
You have a few things wrong here:
The structure of your app is not great, eg. seperate out your API calls
You are calling a lifecycle method, these are called automatically
Don't use DOM selectors like getElementById in React
Use camelcase event listeners (onclick should be onClick)
Use callbacks with event listeners or they will fire immediatly
You included your API key on stackoverflow, big mistake!
Try this:
https://codepen.io/alexplummer/pen/YzwyJOW
import React, {useState} from "react";
const MovielistSearch = props => (
<>
<input type="search" onChange={e => props.saveSearchInput(e.target.value)} />
<button type="button" onClick={() => props.handleSearch()}>
Search
</button>
</>
);
const getMovies = props => {
return ['Title 1', 'Title 2', 'Title 3'];
// ADD REAL API HERE
// const API_KEY = '';
// const getMovies = await axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${props.searchTerm}`);
// return getMovies.data;
}
const MovieList = props => (
<ul>
{props.foundMovies.map(thisMovie=><li>{thisMovie}</li>)}
</ul>
);
const App = () => {
const [searchInput, setSearchInput] = useState("");
const [foundMovies, setFoundMovies] = useState([]);
const movieSearch = ()=> {
if (searchInput == null) return;
const foundMovies = getMovies(searchInput);
setFoundMovies(foundMovies);
}
return (
<div>
<h1 id="title">Movie list</h1>
<MovielistSearch saveSearchInput={setSearchInput} handleSearch={movieSearch} />
<MovieList foundMovies={foundMovies} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
The problem might be here:
const query = document.getElementById('search');
It returns an HTML element. Try document.getElementById('search').innerText

React HOC with invoked functions; "Functions are not valid as a React child"

Please see this codesandbox. I am trying to create a react HOC that receives both a functional component and a function that gets invoked in the HOC.
To do this, I need to create a react component that returns this HOC. The function that needs to be invoked in the HOC needs to share it's scope with the react component, since it might do things like change the component's state or make api calls. My attempt at this is is trying to create a nested react component, but when I do, I get the following error:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it in SampleComponent
How can I create a react HOC that accepts both a react component and a function to be invoked? Thanks! The code can also be found below:
index.tsx:
import * as React from "react";
import { render } from "react-dom";
import SampleComponent from "./sampleComponent";
render(<SampleComponent />, document.getElementById("root"));
sampleWrapper.tsx:
import * as React from "react";
const sampleWrapper = (
WrappedComponent: React.FC,
onButtonClick: () => void
): React.FC => {
const Component: React.FC = () => (
<div>
<button onClick={onButtonClick} type="button">
Click Me
</button>
<WrappedComponent />
</div>
);
return Component;
};
export default sampleWrapper;
sampleComponent.tsx:
import * as React from "react";
import sampleWrapper from "./sampleWrapper";
const SampleComponent: React.FC = () => {
const [title, setTitle] = React.useState("hello world");
const handleTitleChange = (): void => {
setTitle("This is a new title");
};
const SampleInnerComponent: React.FC = () => <h1>{title}</h1>;
return sampleWrapper(SampleInnerComponent, handleTitleChange);
};
export default SampleComponent;
As your sampleWrapper returns a functional component. What you need to do is save the returned functional component into a variable and render the component the same way you do functional component. i.e
import * as React from "react";
import sampleWrapper from "./sampleWrapper";
const SampleComponent: React.FC = () => {
const [title, setTitle] = React.useState("hello world");
const handleTitleChange = (): void => {
setTitle("This is a new title");
};
const SampleInnerComponent: React.FC = () => <h1>{title}</h1>;
const ReturnedSampleComponent = sampleWrapper(SampleInnerComponent, handleTitleChange);
return <ReturnedSampleComponent />;
};
export default SampleComponent;
You can check this codesandbox
It seems you are not returning the React Element, but the Component. You want to return the element React.ReactElemnt. This is what you want i think:
const sampleWrapper = (
WrappedComponent: React.FC,
onButtonClick: () => void
): React.FC => {
return (<div>
<button onClick={onButtonClick} type="button">
Click Me
</button>
<WrappedComponent />
</div>);
};
another alternative :
import * as React from "react";
const sampleWrapper = (
WrappedComponent: React.FC,
onButtonClick: () => void
): React.FC => {
const Component: React.FC = () => (
<div>
<button onClick={onButtonClick} type="button">
Click Me
</button>
<WrappedComponent />
</div>
);
return <Component/>;
};
export default sampleWrapper;

Error 'Target container is not a DOM element' importing react component

I want to try to test react hook (in this case it isn't hook yet, I just tried simple function as component). I have this index.js
import React, { useState } from "react";
export const Counter = () => {
const [count, setCount] = useState(0);
function increaseCount() {
setCount(count => count + 1);
}
return (
<div>
<h1 data-count>count: {count}</h1>
<button onClick={increaseCount}>increase</button>
</div>
);
};
function App() {
return (
<div className="App">
<Counter />
</div>
);
}
export default App;
The app run well, then I add this index.spec.js
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import { Counter } from "./index"; //this line caused problem
test("Test", () => {
console.log("Counter", Counter);
});
Then I got error of
Invariant Violation: Target container is not a DOM element.
What's wrong?
Update:
I separated Counter into another file, it worked, I wonder why I can't use multiple import in App.js
I guess you need to use it instead of test
here you can checkout

How to get event from components to container in React Redux

I'm new to Redux.
I handled the basic Facebook Flux architecture very easily and made some nice app with it.
But I struggle very hard to get very simple Redux App to work.
My main concern is about containers and the way they catch events from components.
I have this very simple App :
CONTAINER
import { connect } from 'react-redux'
import {changevalue} from 'actions'
import App from 'components/App'
const mapStateToProps = (state) => {
return {
selector:state.value
}
}
const mapDispatchToProps = (dispatch) => {
return {
onClick: (e) => {
console.log(e)
dispatch(changeValue())
}
}
}
const AppContainer = connect(
mapStateToProps,
mapDispatchToProps
)(App)
export default AppContainer;
Component
import React, {Component} from 'react'
import Selector from 'components/Selector'
import Displayer from 'components/Displayer'
const App = (selector, onClick) => (
<div>
<Selector onClick={(e) => onClick}/>
<Displayer />
</div>
)
export default App;
CHILD COMPONENT
import React, {Component} from 'react'
const Selector = ({onClick}) => (
<div onClick={onClick}>click me</div>
)
export default Selector;
onClick event does not reach the container's mapDispatchToProps.
I feel that if I get this work, I get a revelation, and finally get the Redux thing! ;)
Can anybody help me get this, please ? (The Redux doc is TOTALLY NOT helpfull...)
The problem is in the App component. In the onClick property of the Selector component, you're passing a function which returns the definition of a function, not the result.
const App = (selector, onClick) => (
<div>
<Selector onClick={(e) => onClick}/> // here is the problem
<Displayer />
</div>
)
You should simply do this instead:
const App = (selector, onClick) => (
<div>
<Selector onClick={(e) => onClick(e)}/>
<Displayer />
</div>
)
Or even simpler:
const App = (selector, onClick) => (
<div>
<Selector onClick={onClick}/>
<Displayer />
</div>
)

Categories

Resources