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.
Related
I have my state and I want to display the component if the value is true but in the console I receive the error message Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state my code
import React, { useState} from "react";
import { useToasts } from "react-toast-notifications";
const Index = () => {
const [test, setTest]= useState(true);
const { addToast } = useToasts();
function RenderToast() {
return (
<div>
{ addToast('message') }
</div>
)}
return (
<div>
{test && <RenderToast /> }
</div>
)
}
You cannot set state during a render. And I'm guessing that addToast internally sets some state.
And looking at the docs for that library, you don't explicitly render the toasts. You just call addToast and then the <ToastProvider/> farther up in the tree shows them.
So to make this simple example works where a toast is shown on mount, you should use an effect to add the toast after the first render, and make sure your component is wrapped by <ToastProvider>
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
addToast('message')
}, [])
return <>Some Content here</>
}
// Example app that includes the toast provider
const MyApp = () => {
<ToastProvider>
<Index />
</ToastProvider>
}
how i can display the toast based on a variable for exemple display toast after receive error on backend?
You simply call addToast where you are handling your server communication.
For example:
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
fetchDataFromApi()
.then(data => ...)
.catch(error => addToast(`error: ${error}`))
}, [])
//...
}
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 }) => { ... }
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
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...
Summary: I am trying to pass a function down to a presentational component that will dispatch an action. The first argument (a key) must be defined in the container. A second argument (a page number) would be supplied in the presentational component. I'm suspecting I am doing something dumb.
I'm new to this so forgive me if my terminology is off or the example is poor. Here is what's going on:
const mapDispatchToProps = (dispatch) => ({
changePage: bindActionCreators(changePosts, dispatch)
})
The purpose of this function is to change the page number of a certain group of posts in the store.
export const changePage = key => (dispatch, getState) => page => {
return dispatch(onChangePage(key, page)); //changes the page of the key obj
}
What I was hoping to do was pass this down to a presentational component like so:
<Posts changePage={this.props.changePosts('news') />
Now inside the Posts component all I would have to do is
<a onClick={this.props.changePage(4)}>4</a>
to go to page 4. Essentially I'm telling the presentational component to change to page 4, but only where the key is 'news', which I defined a level up in the container.
This isn't working. The reason it isn't is because a 4 isn't being passed, but instead a Proxy Object:
[[Handler]]:Object
[[Target]]:SyntheticMouseEvent
[[IsRevoked]]:false
Am I misunderstanding how currying/dispatch works? I really appreciate any help.
Edit: more code
// container
class Index extends Component {
static async getInitialProps({ store, isServer }) {
await store.dispatch(fetchPosts('news'));
return { ...store.getState() };
}
constructor(props) {
super(props);
}
render() {
const { nextPage, previousPage, changePage } = this.props;
const { posts } = this.props.wordpress;
const { news } = posts;
return (
<Main>
<Posts
next={()=>nextPage('news')}
previous={()=>previousPage('news')}
changePosts={()=>changePage('news')}
items={news.items}
/>
</Main>
);
}
}
const mapStateToProps = ({ wordpress }) => ({
wordpress
})
const mapDispatchToProps = (dispatch) => ({
fetchPosts: bindActionCreators(fetchPosts, dispatch),
nextPage: bindActionCreators(nextPosts, dispatch),
previousPage: bindActionCreators(previousPosts, dispatch),
changePage: bindActionCreators(changePosts, dispatch)
})
export default withRedux(initStore, mapStateToProps, mapDispatchToProps)(Index);
// actions
export const changePage = key => (dispatch, getState) => page => {
console.log(page) // this returns a proxy object for some reason
return dispatch(changePage(key, page));
}
// The only real thing in the presentational component
<a onClick={props.changePosts(4)}>4</a>
Previous and Next buttons work
1) Firstly I should mention that mapDispatchToProps in your example looks a bit weird, because you only need to use bindActionCreators in case if you supply it with an object of actions. Most likely a simple object form of mapDispatchToProps would be enough in your case:
const mapDispatchToProps = {
fetchPosts,
nextPage,
previousPage,
changePage,
};
2) Secondly the following piece of code is really confusing, why is it dispatching itself recursively?
// actions
export const changePage = key => (dispatch, getState) => page => {
console.log(page) // this returns a proxy object for some reason
return dispatch(changePage(key, page));
}
3) Finally, provided you already have an action creator changePage somewhere in you code that accepts args key and page, and it was added to mapDispatchToProps as I've mentioned earlier:
// in your container
<Posts
...
changePosts={page => props.changePage('news', page)}
...
/>
// in your component
<a onClick={() => props.onClick(4)}>4</a>