How to get event from components to container in React Redux - javascript

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>
)

Related

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

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.

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;

Test a react component using test and react-testing-library

I just joined a team where we use react, redux, recompose to construct components to build UI. There aren't any unit tests in the application and there isn't consistent architecture for the application. I decided to take it upon myself to add unit tests using jest and react-testing-library. I succeed with few snapshot tests but I am struggling with unit testing. I am still learning react and pretty new to redux. I would love some suggestion. I am going to share a component which renders a table with column and row. I would love a feedback.
import React, { useEffect, useState } from 'react';
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { clearAll, fetchContacts } from '~/store/resources/contacts/actions';
import { isDevEnv } from '~/utils';
import Sidebar from './Sidebar';
import Table from './Table';
import Toolbar from './Toolbar';
const Contacts = ({ clearAll, fetchContacts, ...props }) => {
const [searchValue, setSearchValue] = useState('');
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [canonicalFormValues, setCanonicalFormValues] = useState({ active: true });
useEffect(() => {
fetchContacts();
return () => {
clearAll();
};
}, []);
const closeSidebar = () => {
if (isDevEnv) {
console.log('hit close function');
}
setIsSidebarOpen(false);
};
return (
<div>
<Toolbar
searchValue={searchValue}
setSearchValue={setSearchValue}
setIsSidebarOpen={setIsSidebarOpen}
/>
<Table setCanonicalFormValues={setCanonicalFormValues} />
<Sidebar
isSidebarOpen={isSidebarOpen}
closeSidebar={closeSidebar}
canonicalFormValues={canonicalFormValues}
/>
{isDevEnv && (
<div>
This is coming from the contact folder
<br />
state values:
<br />
{JSON.stringify({ searchValue })}
<br />
{JSON.stringify({ isSidebarOpen })}
<br />
{JSON.stringify({ canonicalFormValues })}
</div>
)}
</div>
);
};
const mapDispatchToProps = {
clearAll,
fetchContacts,
};
export default compose(
connect(
null,
mapDispatchToProps,
),
)(Contacts);
I generally start out with a simple "should render without crashing" test. I prefer to export and test the undecorated component, in your case Contacts.
export const Contacts = ({ clearAll, fetchContacts, ...props }) => { ...
In the test file
import React from 'react';
import { render } from '#testing-library/react';
import { Contacts } from '.';
// mock the other imported components, they should already be tested alone, right?
jest.mock('./Sidebar');
jest.mock('./Table');
jest.mock('./Toolbar');
describe('Contacts', () => {
it('should render without crashing', () = {
render(
<Contacts
// pass all the props necessary for a basic render
clearAll={jest.fn()}
fetchContacts={jest.fn()}
/>
);
});
});
At this point I run a code coverage report to see how much I have, then add more tests with varying prop values and/or using the react-testing-library's matchers to target buttons or elements to assert text is visible or trigger callbacks, etc, until I have the coverage I want.
Sometimes some of your components may rely on context provider, and in this case RTL allows you to specify wrappers. For example if your component gets decorated with react-intl for string localization, you can provide a wrapper.
export const Contacts = ({ clearAll, fetchContacts, intl }) => { ...
...
export default compose(
connect(
null,
mapDispatchToProps,
),
injectIntl,
)(Contacts);
Create a wrapper
import { IntlProvider } from 'react-intl';
const IntlWrapper = ({ children }) => (
<IntlProvider locale="en">{children}</IntlProvider>
);
const intlMock = {
...
formatMessage: message => message,
...
};
and to test, specify the wrapper in the render options argument
render(
<Contacts
// pass all the props necessary for a basic render
clearAll={jest.fn()}
fetchContacts={jest.fn()}
intl={intlMock}
/>,
{
wrapper: IntlWrapper
}
);
react-testing-library has a lot of documentation, but it is worth reading through. Hope this helps you get going.

Can I mapDispatchToProps without mapStateToProps in Redux?

I am breaking apart Redux' todo example to try to understand it. I read that mapDispatchToProps allows you to map dispatch actions as props, so I thought of rewriting addTodo.js to use mapDispatchToProps instead of calling dispatch(addTodo()). I called it addingTodo(). Something like this:
import React from 'react';
import {connect} from 'react-redux';
import addTodo from '../actions';
let AddTodo = ({addingTodo}) => {
let input;
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
addingTodo(input.value)
input.value = ""
}}>
<input ref={node => {
input = node
}} />
<button type="submit">Submit</button>
</form>
</div>
)
}
const mapDispatchToProps = {
addingTodo: addTodo
}
AddTodo = connect(
mapDispatchToProps
)(AddTodo)
export default AddTodo
However, when I run the app, I get this error: Error: Invalid value of type object for mapStateToProps argument when connecting component AddTodo.. I never used mapStateToProps to begin with on AddTodo component, so I was not sure what was wrong. My gut feeling says that connect() expects mapStateToProps to precede mapDispatchToProps.
The working original looks like this:
import React from 'react';
import {connect} from 'react-redux';
import addTodo from '../actions';
let AddTodo = ({dispatch}) => {
let input;
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ""
}}>
<input ref={node => {
input = node
}} />
<button type="submit">Submit</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
export default AddTodo
Complete repo can be found here.
So my question is, is it possible to do mapDispatchToProps without mapStateToProps? Is what I am trying to do an acceptable practice - if not, why not?
Yes, you can. Just pass null as first argument:
AddTodo = connect(
null,
mapDispatchToProps
)(AddTodo)
Yes, it's not just acceptable practice, it's recommended way to trigger actions. Using mapDispatchToProps allows to hide the fact of using redux inside your react components

Categories

Resources