Trouble passing props from functional to functional component - javascript

I am passing props to a functional component, but I keep getting an error:
const renderItem = ({ item }) => (
<CommentCellClass
key={item.key}
commentLikes={item.commentLikes}
.... more props
I try and access them in the CommentCellClass component:
const CommentCellClass = ({ props, navigation }) => {
const { key, commentLikes } = props;
But I get the following error:
TypeError: undefined is not an object (evaluating 'props.key')]
What am I doing wrong? The props are not null (I checked before I passed them to commentCellClass)
Sorry for the confusing name (CommentCellClass is a functional component). We are in the process of converting the class components to functional components in our app.

Where does navigation come from? I would expect your code to look like this:
const CommentCellClass = (props) => {
const { key, commentLikes } = props;
...
}
or
const CommentCellClass = ({ key, commentLikes }) => { ... }

Related

How to implement your own "connect" function with Redux and React

I'm trying to implement my own custom way of passing props down to a component.
What I am trying to achieve is something similar to the connect()(Component) method, but with my own little twist.
Say I have a custom component, such as,
// presentationalComponent.js
const PresentationalComponent = ({
name,
loading,
}) => {
return (
<div>
This component is: {name}. The current state is: {loading}
</div>
)
}
and an Error component, such as,
// error.js
const Error = ({errors}) => {
return (
errors.forEach(error => {
<div>{error}</div>
})
)
}
I would want to wrap every component with a morph method, which would grab the requested state and also return a parent component.
So if I was to do,
const PresentationalComponent = ({ . . .
. . .
}
export default morph('layout', ['loading'])(PresentationalComponent)
I would want the morph method to return something like,
const getState = (reducer, values) =>
// lodash
_.pick(
useSelector((state) => state[reducer]),
values,
)
const ParentContainer = ({Component, requestedState: {reducer, fields}}) => {
const errors = useSelector(({ errors }) => errors)
const state = getState(reducer, fields)
if (errors.length) return <Error errors={errors} />
return <Component state={state} />
}
But I'm not sure how to go about this, because hooks like useSelector can only be used in functional components. I'm not sure how connect from Redux makes it work.
Ideally, if I was to do,
const Home = ({ state }) => {
<PresentationalComponent name='My presentational component!' />;
};
I would want to see This component is: My presentational component!. The current state is: false.
How can I achieve this with exporting my components such as morph('reducer', ['state'])(Component)?
I've tried something like,
const morph = (reducer, state) => (Component) => {
console.log('reducer', reducer);
console.log('state', state);
return Component;
};
export default morph;
But that didn't work.

Difference in accessing props after change in redux store between React class component and React functional component

I`m having a problem in understanding why in a Class based component vs Functional component the same steps yield a different result. Below I have an example that presents this difference.
class Test extends Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields(async (err, values) => {
await this.props.saveUserDetailsAction(values);
const { error } = this.props; // expected modified prop
if (!error.status) {
this.props.router.push("/route");
}
});
};
}
const mapStateToProps = ({ app }) => ({
error: app.error
});
const mapDispatchToProps = {
saveUserDetailsAction,
};
export default compose(
withRouter,
connect(
mapStateToProps,
mapDispatchToProps
)
)(Test);
The current error prop inside the class component reflects the changes in the redux store after the async function saveUserDetailsAction has completed, in this case dispatching an action if the request fails.
Accessing the error props after the saveUserDetailsAction function shows the updated prop.
The same code written as a functional component does not yield the same result. Accessing the error prop after the saveUserDetailsAction function completed doesn't reflect the change in the store yet.
const Test = (props) => {
const handleSubmit = e => {
e.preventDefault();
props.form.validateFields(async (err, values) => {
await props.saveUserDetailsAction(values);
const { error } = props; // unchanged prop when accessed here
if (!error.status) {
props.router.push("/route");
}
});
};
}
const mapStateToProps = ({ app }) => ({
error: app.error
});
const mapDispatchToProps = {
saveUserDetailsAction,
};
export default compose(
withRouter,
connect(
mapStateToProps,
mapDispatchToProps
)
)(Test);
To my point of view, it's an anti-pattern, you need to wait for another cycle to get the correct error props. Functional component is the right behavior.
What you need to do is find a way to return error here :
const { error } = await props.saveUserDetailsAction(values);
Redux store error variable can still be used in another component
Other way to do it is to use a useEffect(() => {}, [error]) and to distinguish 3 states "No Submitted Yet", "Submitted without error", "Submitted with error" but i don't recommend it

With a React Hooks' setter, how can I set data before the component renders?

I export a JS object called Products to this file, just to replace a real API call initially while I am building/testing. I want to set the function's state to the object, but mapped. I have the component looking like this:
function App() {
const [rooms, setRooms] = useState([]);
const [days, setDays] = useState([]);
const roomsMapped = products.data.map(room => ({
id: room.id,
title: room.title
}))
useEffect(() => {
setRooms(roomsMapped);
})
return ( etc )
This returns the following error: Error: Maximum update depth exceeded.
I feel like I'm missing something really obvious here, but am pretty new to React and Hooks. How can I set this data before the component renders?
Just declare it as initial value of rooms
const Component = () =>{
const [rooms, setRooms] = useState(products.data.map(room => ({
id: room.id,
title: room.title
})))
}
You can also use lazy initial state to avoid reprocessing the initial value on each render
const Component = () =>{
const [rooms, setRooms] = useState(() => products.data.map(room => ({
id: room.id,
title: room.title
})))
}
Change useEffect to this
useEffect(() => {
setRooms(roomsMapped);
},[])
With Lazy initialisation with function as a parameter of useState
import React, { useState } from "react";
function App() {
const [rooms, setRooms] = useState(() => {
// May be a long computation initialization
const data = products.data || [];
return data.map(({ id, title }) => ({ id, title }));
});
return (
// JSX stuffs
)
}
You can use default props for this.set initial value with empty list .
You are getting 'Error: Maximum update depth exceeded', because your useEffect function doesn't have dependency array. Best way to fix this is to pass empty array as the second argument to useEffect like this:
useEffect(() => {
setRooms(roomsMapped);
},[]) <= pass empty array here
this will prevent component to re render, it you want your component to re render on props change you can pass the props in the array like this:
useEffect(() => {
setRooms(roomsMapped);
},[props.props1,props.props2])
here you can pass as many props as you want...

Pass string value as object to Redux component with PropTypes in React

A component using Redux example from React Final Form was created in index.js.
<FormStateFromRedux valueName="valueString" form="counter" />
It is using redux to track values, using prop-types it should receive object name to return exact object value:
const FormStateFromRedux = ({ state, valueName }) => (
<p>{state.values.valueName}</p>
)
export default connect((state, ownProps) => ({
state: getFormState(state, ownProps.form)
}))(FormStateFromRedux)
FormStateFromRedux.propTypes = {
valueName: PropTypes.objectOf(PropTypes.string)
};
It is possible to return value when object path is set manually, here I want to make reusable component to return values using PropTypes but don't know what kind of type to choose. Is it possible to pass value as string? May someone know what should be correct approach in this case?
UPDATE
It is possible to get string value inside object using []
const FormStateFromRedux = ({ state, valueName }) => (
<p>{state.values[valueName]}</p>
)
You should avoid passing whole form state to component. Component should have only necessary props.
const FormStateFromRedux = ({ valueName }) => ( <p>{valueName}</p> );
export default connect((state, props) => {
const formState = getFormState(state, props.form);
const { value : { valueName } } = formState;
return { valueName };
})(FormStateFromRedux);
FormStateFromRedux.propTypes = {
valueName: PropTypes.string
};

Pass mobx store and props to function

I have a React component which calls a function, and i need to pass the injected mobx store and the components props into this function
const Component = inject("store")(observer(({store, props}) => {
return (
<div>
{_someRenderFunction(store, props)}
</div>
);
}));
It's used in the function like this
function _someRenderFunction(store, props) {
let data = store.data;
const { cookies } = props;
...
}
But i get the error Cannot read property 'cookies' of undefined
How can i pass both the store and component props to the _someRenderFunction?
The problem is this line:
({store, props}) => {};
Your component receives only props so the basic definition would be:
(props) => {}
Inject gives you your injected store inside the given props. So what you receive is:
const props = { store: STORE_INSTANCE }
With the object destructing you can extract properties from props. So this would also work:
({ store }) => {}
Here you are extracting the property store from your props. but in your example you are also extracting the property props from props. So in your case props should look like:
const props = { store: STORE_INSTANCE, props: PROPS_OBJECT }
This is not what you want. So object destructing is not what you want in this case. The following code should work:
const Component = inject("store")(observer((props) => {
return (
<div>
{_someRenderFunction(props)}
</div>
);
}));
function _someRenderFunction(props) {
const { cookies, store } = props;
let data = store.data;
...
}

Categories