Passing Props between components - reactjs

I am creating a bit of a playground to learn react and I've hit a road block with passing props between components. I essentially have two components, 1 that is the base component and then another that renders it out on the page with some extras (which i've removed for simplicity sake). I essentially want to be able to reuse the items in other places.
I'd like to be able to, when rendering the component in the example specify if it is type=submit if nothing specified default to type=button.
I'm clearly missing the point here because I get the error Cannot read property 'props' of undefined with the below code. Any help would be appreciated
Button Component
import React, {PropTypes} from 'react';
import './button_component.scss';
const propTypes = {
type: PropTypes.string
}
const ButtonComponent = () => {
return <button type={this.props.type}>button</button>
}
ButtonComponent.propTypes = propTypes;
export default ButtonComponent;
Then I have a component that outputs my item
import React from 'react';
import ButtonComponent from './button_component';
import Example from './example'
export default () =>
<Example>
<ButtonComponent type='button' />
</Example>;

ButtonComponent is a functional component. Hence, you can not use this.props in its code.
You should introduce props argument
const ButtonComponent = (props) => {
return <button type={props.type}>button</button>
}
Object destructuring and defaultProps can help you make your code simpler
const ButtonComponent = ({ type }) => {
return <button type={type}>button</button>
}
ButtonComponent.defaultProps = {
type: 'button'
}
then you can simply put <ButtonComponent /> to render a button.

Related

Exporting const from a fucntion in React into another component?

I have a file ButtonExample.js that I am exporting a component (ButtonExample) and const (color) from. I am attempting to import them into a file called CustomCreation.js. However, on compiling I am met with the error:
Attempted import error: 'color' is not exported from './ButtonExample'.
This is the code for ButtonExample.js
import React, { useState } from "react";
import { GithubPicker } from "react-color";
function ButtonExample() {
const [showColorPicker, setShowColorPicker] = useState(false);
const [color, setColor] = useState("");
return (
<div>
<button
type="button"
class="btn btn-dark"
onClick={() =>
setShowColorPicker((showColorPicker) => !showColorPicker)
}
>
{showColorPicker ? "Close" : "Choose Color"}
</button>
{showColorPicker && (
<div>
<GithubPicker
color={color}
onChange={(updatedColor) => setColor(updatedColor.hex)}
colors={[
"#131313" /*black*/,
"#575757" /*grey*/,
"#f6f6f6" /*white*/,
"#203e20" /*green*/,
"#423122" /*brown*/,
"#121c4f" /*navy*/,
]}
/>
</div>
)}
</div>
);
}
export default { ButtonExample, color };
This is how I'm attempting to import it in CustomCreation.js
import ButtonExample, { color } from "./ButtonExample";
I've done this in the past and it's worked fine so I'm just unsure if I'm missing something really obvious.
color is declared as state inside the scope of the ButtonExample() functional component, so when you try to access it inside export default { ButtonExample, color }; it'll always be undefined.
Moreover, your export and import syntax seems incorrect. A default export defines a single export per module, so what you're essentially doing here is exporting an object comprised of ButtonExample and an undefined variable and attempting to use both named and default imports to import them.
To better share state between your components, you should either use a state manager like Redux to manage state globally throughout your application or implement a parent component for this component and any other component that relies on the current color. The parent component will then manage the color state and pass that down as a prop to any other components that need it. Additionally, the parent component will need to implement its own setColor function that it passes to ButtonExample as a prop, which can then be substituted for GithubPicker's onChange callback.

On click returns null instead of an object

It's really basic I guess. I'm trying to add onClick callback to my script & I believe I'm missing a value that would be responsible for finding the actual item.
Main script
import React from 'react';
import { CSVLink } from 'react-csv';
import { data } from 'constants/data';
import GetAppIcon from '#material-ui/icons/GetApp';
import PropTypes from 'prop-types';
const handleClick = (callback) => {
callback(callback);
};
const DownloadData = (props) => {
const { callback } = props;
return (
<>
<CSVLink
data={data}
onClick={() => handleClick(callback)}
>
<GetAppIcon />
</CSVLink>
</>
);
};
DownloadData.propTypes = {
callback: PropTypes.func.isRequired,
};
export default DownloadData;
Storybook code
import React from 'react';
import DownloadData from 'common/components/DownloadData';
import { data } from 'constants/data';
import { action } from '#storybook/addon-actions';
export default {
title: 'DownloadData',
component: DownloadData,
};
export const download = () => (
<DownloadData
data={data}
callback={action('icon-clicked')}
/>
);
So right now with this code on click in the storybook I'd get null and I'm looking for an object.
One of the potential issues I can see is that your handleClick function is stored as it is in-memory, when you import the component. That means you're keeping reference of something that doesn't exists and expects to use it when rendering the component with the callback prop.
Each instance of a component should have its own function. To fix it, move the function declaration inside the component. Like this:
const Foo = ({ callback }) => {
// handleClick needs to be inside here
const handleClick = callback => {
console.log("clicked");
callback(callback);
};
return <div onClick={() => handleClick(callback)}>Click me!</div>;
};
Check this example.
If this doesn't fix your problem, then there is something wrong with how you're implementing Storybook. Like a missing context.

Recompose multiple renderComponent

I have a recompose filter that needs to render two components and pass props from a redux connect to the second component
However the code below never renders the second renderComponent - which is a real shame. Is there a way to get the below working, or should I opt for a regular React component?
import { compose, renderComponent } from "recompose"
import { connect } from "react-redux"
import Filters from "./filter/filter"
import Wrestlers from "./container"
const defaultState = state => ({
collection: state.roster,
})
export default compose(
renderComponent(Filters),
connect(defaultState),
renderComponent(Wrestlers),
)(Wrestlers)
renderComponent always discards the second argument (the base component) and renders the first argument. If you want to render then both, just create a new component and render them. Probably something like:
const Parent = ({ collection }) => (
// You can return an array here if you are using React 16
<div>
<Filters />
<Wrestlers collection={collection} />
<div>
)
export default connect(defaultState)(Parent)

Testing react components that have JSS injected styles with enzyme

I'm having a react component. Let's say Todo
import React, { Component } from 'react';
import injectSheet from 'react-jss';
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export default injectSheet(Todo);
I want to test it with enzyme. And there are two problems with it.
1. Access to the state
(and other component specific features)
When I shallow or mount that composer in the suite I can't get access to its state of course because it's not my component anymore but something new around it.
E.g. this code will give me an error:
it('should have state updated on handleAddTodo', () => {
const todo = shallow(<Todo />);
const length = todo.state('todos').length;
});
It says of course TypeError: Cannot read property 'length' of undefined because the state is not what I expect but this: { theme: {}, dynamicSheet: undefined }
This won't also give me access to props, refs etc.
2. Problems with theme provider
To provide some default colouring to the project like this:
import React, { Component } from 'react';
import Colors from './whatever/Colors';
class App extends Component {
render() {
return (
<ThemeProvider theme={Colors}>
<WhateverInside />
</ThemeProvider>
);
}
}
And of course when running tests it gives me an error [undefined] Please use ThemeProvider to be able to use WithTheme.
So my question is the following. Is there a way to solve this problem in “one single place”. How can I make enzyme agnostic of what is my component wrapped with?
If not, then how do I solve the problem if passing the ThemeProvider features down to the component that I'm testing?
And how can I access the state, ref, props and other things of the wrapped component?
Thank you!
here's what I'd do to test the component,
import React, { Component } from 'react';
import injectSheet from 'react-jss';
const styles = {};
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export { styles, Todo as TodoCmp }
export default injectSheet(styles)(Todo);
and in the test file, I'd add the following:
import { theme } from 'your-theme-source';
const mockReducer = (prev, curr) => Object.assign({}, prev, { [curr]: curr });
const coerceStyles = styles =>
typeof styles === 'function' ? styles(theme) : styles;
const mockClasses = styles =>
Object.keys(coerceStyles(styles)).reduce(mockReducer, {});
import {TodoCmp, styles} from 'your-js-file';
// then test as you'd normally.
it('should blah blah', () => {
const classes = mockClasses(styles);
const todo = shallow(<Todo classes={classes} />);
const length = todo.state('todos').length;
})
Please read more about it in the nordnet-ui-kit library specifically in the test directory. Here's a quick example
It is not related to JSS specifically. Any HOC wraps your component. Ideally you don't test any internals of a component directly.
Components public api is props, use them to render your component with a specific state and verify the rendered output with shallow renderer.
For some edge cases if first and preferred way is impossible, you can access the inner component directly and access whatever you need directly. You will have to mock the props the HOC would pass otherwise for you.
const StyledComponent = injectSheet(styles)(InnerComponent)
console.log(StyledComponent.InnerComponent)
If your component relies on theming, you have to provide a theme provider, always.

React + Redux: Separating the presentation from the data

I am building a weather app with React & Redux. I've decided to venture into uncharted waters as a noob to React & Redux. I'm splitting things up into presentational components and their respective container that will handle the data. I'm having some problems wrapping my head around this though. It might come down to how I'm trying to do it I'm just really unsure.
Right now I have SearchBar, CurrentWeather, & Forecast components and an AppContainer that I'm trying to integrate those components into. I have the SearchBar component integrated into the AppContainer so far and it is working with no problems. Here is where I am getting confused. So I have provided the needed actions and components to the container and the container has been connected so when the user does a search the api call will be made and the state will update through the reducers.
That data should be available through mapStateToProps now correct?
How can I go about using that data after the user has performed the action but have it not be used upon the initial render? If AppContainer is rendering these three components I will obviously be passing props to them so they render and function as they are expected to. I'm thinking this is where a lifecycle could be used I'm just unsure of which or how to use them. My code for the AppContainer, SearcBar, & CurrentWeather are below. CurrentWeather & Forecast are nearly identical (only providing different data from different endpoints for the api) so I did not provide it. I also didn't provide the actions or reducers because I know they work fine before I decided to attempt this refactor. Maybe I need more than one container to pull this off? Any advice or direction would be greatly appreciated, thanks all and have a good night.
** Do have a side question: on _weatherSearch I have event.preventDefault(); because the SearchBar is a form element. Do I even need to provide this? If event is not what is being passed but the term I think no. The event is being used as seen below in the form element of SearchBar:
onSubmit={event => getWeather(event.target.value)}
App Container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCurrentWeather, fetchForecast } from '../actions/actions';
import SearchBar from '../components/SearchBar';
import CurrentWeather from '../components/CurrentWeather';
class AppContainer extends Component {
_weatherSearch(term) {
event.preventDefault();
// Here is where we go to fetch weather data.
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => {this._weatherSearch(term);};
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
<CurrentWeather />
</div>
);
}
}
const mapStateToProps = ({ current, forecast }) => {
return {
current,
forecast
}
}
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
SearchBar:
import React from 'react';
const SearchBar = ({ getWeather }) => {
return(
<form className='input-group' onSubmit={event => getWeather(event.target.value)}>
<input
className='form-control'
placeholder='Search a US City' />
<span className='input-group-btn'>
<button className='btn btn-secondary' type='submit'>Submit</button>
</span>
</form>
);
}
export default SearchBar;
CurrentWeather: *NOTE: I have not removed any of the logic or data processing from CurrentWeather yet so it has not been refactored to a presentational only component yet.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {unitConverter} from '../conversions/conversions_2.0';
export class CurrentWeather extends Component {
_renderCurrentWeather(cityData) {
const name = cityData.name;
const {temp, pressure, humidity} = cityData.main;
const {speed, deg} = cityData.wind;
const {sunrise, sunset} = cityData.sys;
return (
<tr key={name}>
<td>{unitConverter.toFarenheit(temp)} F</td>
<td>{unitConverter.toInchesHG(pressure)}"</td>
<td>{humidity}%</td>
<td>{unitConverter.toMPH(speed)}mph {unitConverter.toCardinal(deg)}</td>
</tr>
);
}
render() {
let currentWeatherData = [];
if (this.props.current) {
currentWeatherData = this.props.current.map(this._renderCurrentWeather);
}
return (
<table className="table table-reflow">
<thead>
<tr>
<th>Temperature</th>
<th>Pressure</th>
<th>Humidity</th>
<th>Wind</th>
</tr>
</thead>
<tbody>
{currentWeatherData}
</tbody>
</table>
);
}
}
function mapStateToProps({current}) {
return {current};
}
export default connect(mapStateToProps)(CurrentWeather);
Your render function is very dynamic. You can omit anything you like:
class AppContainer extends Component {
_weatherSearch(term) {
// event.preventDefault(); We can't do this because we don't have an event here...
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => { this._weatherSearch(term); };
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
{ Boolean(this.props.current) && <CurrentWeather /> }
</div>
);
}
}
const mapStateToProps = ({ current }) => ({ current });
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
This is how you deal with missing data. You just either show nothing, or a message to search first, or if it's loading,you can show a spinner or throbber.
The technique used above to hide CurrentWeather is to pass a Boolean to React if we're wanting to hide the component. React ignores true, false, null and undefined.
Note that it's a good idea to only ever pass data in mapStateToProps that you'll actually be using inside the component itself. In your code you're passing current and forecast but you don't use them.
Redux will rerender when any of the mapStateToProps, mapDispatchToProps or props data changes. By returning data you'll never use you instruct Redux to rerender when it's not necessary.
I'm a react-redux noob myself :-) and I've come across similar issues.
As far as I can tell, the container/presentational separation you've made looks good, but you can go even a step further and separate the container's fetching and mounting.
The solution I'm referring to is what people variously call "higher-order components" and "wrapper components": (the code below isn't tested - it's just for illustration purposes)
import {connect} from blah;
const AppWrap = (Wrapped) => {
class AppWrapper extends Component {
constructor(props) {
super(props);
this.state = {foo: false};
}
componentWillMount() {
this.props.actions.fooAction()
.then(() => this.setState({foo: false}));
}
render() {
return (<Wrapped {...this.props} foo={this.state.foo}/>);
}
}
function mapState(state) { blah }
function mapDispatch(dispatch) { blah }
return connect(mapState, mapDispatch)(AppWrapper);
}
export default AppWrap;
Notice the = (Wrapped) => { part at the top. That is what's doing the actual "wrapping", and the argument can be named anything so long as you refer to it in the render hook.
Now inside your AppContainer, you get a this.props.foo which acts as a flag telling you that fooAction() has completed, and you can use it to render your presentational components accordingly. Until fooAction completes, you can be sure that the foo passed into AppContainer will be false.
To put what I just said into code, your AppContainer might look something like this:
import AppWrapper from './AppWrapper';
class AppContainer extends Component {
constructor(props) {
super(props);
}
render() {
return (!this.props.foo) ? <div>bar</div> : (
<div blah>
<SearchBar blah/>
<CurrentWeather blah/>
</div>
);
}
}
export default AppWrapper(AppContainer);
The benefit of using a wrapper component like this is that
you can take more control over how and when exactly the data gets rendered
account for "loading" mechanisms and logic
avoid quirky problems like having to make dispatches within componentWillMount hooks and having to deal with the consequences.
Take a look at this blog post for more information about HoCs: https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750

Resources