How redux action dispatch differ between class & function component? - reactjs

I would like to know how redux implementation in functional component different from class component.
Because I got a working example and not working example.
The working example is the class component. When I use class component and use props.fetchSomething
Its always works.
here is the class component.
import React, { Component } from "react";
class Posts extends Component {
componentDidMount() {
this.props.fetchPosts();
}
render() {
let postItems = this.props.posts.map((post) => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
));
return <div>{postItems}</div>;
}
}
Now I will show you the not working version of function component.
import React, { useEffect } from "react";
const Posts = ({ fetchPosts, posts }) => {
useEffect(() => {
fetchPosts();
}, []);
let items = posts.map((post) => {
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>;
});
return <div>{items}</div>;
};
As you can see, FROM MY UNDERSTANDING these 2 works the same, Because i fetch the data at mounting state and map the store data.
Here my rest of the code. which are redux operations. I used these below codes for both class and function component. The thing is, only the class component show the list of data. The function component always return an empty array.
WHY IS THAT
Posts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array.isRequired,
newPost: PropTypes.object,
};
const mapStateToProps = (state) => ({
// coming from root reducer
posts: state.posts.itemsArray,
newPost: state.posts.item,
});
export default connect(mapStateToProps, { fetchPosts })(Posts);

Items is not assigned to anything because the callback function passed to map doesn't return anything:
useEffect(() => {
fetchPosts();
//you should really try using create-react app, then
// you'd see the missing dependency when compiling
// When you also set up vs code you'd see it when you
// open the file in the editor
}, [fetchPosts]);
//in VS code you would have a linter warning:
// Expected to return a value in arrow function.
// also defined items as const
const items = posts.map((post) => (//replaced { with (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>//removed ;
)/**replaced } with ) */);
If you use {...} after the arrow of an arrow function then ... is the body of the function and you have to return something from this body:
let items = posts.map((post) => {
return (//have to return jsx
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
); //removed ;
});
You can also have an arrow function that has no body, then whatever comes after the arrow is the return value. For example: const add = (a,b)=>a+b.
If you have a bodyless arrow function that returns an object it gets confusing because an object and a function body have the same syntax {} so to return an object you can do ({}), here is an example: const add = (a,b)=>({sumAB:a+b}). With jsx the (<jsx />) are optional so const hello = () => <div>hello</div>; and const hello = () => (<div>hello</div>); are both valid. When multi line jsx is returned a formatter such as prettier will usually format it with (<jsx />) like so:
const hello = () => (
<div>
<div>hello</div>
</div>
);

Related

React Component always reverts to the last item in array

This is a React/Redux app. I have two components. One nested in the other.
<UserReview>
<UserReviewItem>
</UserReview>
I am working with two APIs. I call one API to get a 'movieId', I use the 'movieId' to call a second API to retrieve an image. I am mapping over an array, but it seems like it is only returning the last element's movieId.
The wrapping component:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
render() {
const allReviews = this.props.reviews.slice(-2).map((review, i) => {
return (
<UserReviewItem
username={review.username}
text={review.text}
key={review._id}
movieId={review.movieId}
/>
)
});
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
Child Component:
class UserReviewItem extends Component {
componentDidMount() {
**this.props.fetchImage(this.props.movieId)**
}
render() {
return (
<div key={this.props.key}>
<img
src={`https://image.tmdb.org/t/p/original/${this.props.img}`}
/>
<div>
<h4>{this.props.username}</h4>
<p>{this.props.text}</p>
</div>
</div>
);
const mapStateToProps = state => ({
img: state.movies.img
})
I want a different image for every item in the array but I am getting the same image even though the usernames and texts are different.
A solution I tried but got the same result:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
render() {
const allReviews = this.props.reviews.slice(-2).map((review, i) => {
return (
<UserReviewItem
username={review.username}
text={review.text}
key={review._id}
-------> movieId={this.props.reviews[i].movieId} <--------
/>
)
});
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
You can try this way:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
renderItems(){
const { reviews } = this.props
if (!reviews) return []
return reviews.map(review => <UserReviewItem
username={review.username}
text={review.text}
key={review._id}
movieId={review.movieId}
/>)
}
render() {
return (
this.props.reviews
? <div>{this.renderItems()}</div>
: <p>Loading...</p>
)
};
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
basically in the renderItems function you destructure the props, get the reviews and map them. In your render function you set a loader if the views are not ready yet (you can use a loading prop if you are setting that up in your store), or call the list if the reviews are already fetched and ready.
I found the answer. Because the second call depends on information from the first call I have to chain the calls using .then()
using a fetch inside another fetch in javascript

Unable to update the text using useState

I am converting a class based component to a functional component
But in functional component state is not getting updated.
Could you please update what might be the issue.
I tried putting a alert & its working
I have converted previously working below code-base :
class Spice extends React.Component {
constructor() {
super();
this.state = {
title: 'Welcome to XYZ '
};
}
nameChangeFunction() {
this.setState({
title: 'Welcome to Authentic XYZ World'
});
}
And the Calling Snippet:
render() {
const spiceList = [
..,
..,
..
];
return (
<div>
<h1>{this.state.title}</h1>
<button className='tc ma5' onClick={() => this.nameChangeFunction()}>
Check Out
</button>
<br />
</div>
);
}
New functional component is created as below, but its not updating the title, onClick.
const SpiceHookBased = () => {
const [title, setTitle] = useState("INITIAL XYZ Title");
const changeTitle = () => {
return setTitle("some value changed");
}
return {
render() {
const spiceList = [
..,
..,
..
];
return (
<div>
<h1>{title}</h1>
<button className='tc ma5' onClick={changeTitle}>
</button>
);
}
}
}
In second case, its not updating new text title.
Kindly suggest a correction
Thanks
In the codesnippet there was multiple return and render. See the below codesnippet and check
Apart from that the code looks fine, you can remove the return and write it in a single line
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
const [title, setTitle] = useState("INITIAL XYZ Title");
const changeTitle = () => setTitle("some value changed")
return (
<div>
<h1>{title}</h1>
<button className="tc ma5" onClick={changeTitle}>
Click Me to change title
</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working codesandbox
You don't need to return the setTitle function in your update function.
Also there is no need for the render() method in functional components, when you return an object like you are doing it just turns the component into a regular function, not a react Component, so that is likely the reason your useState hooks aren't working.
Try changing your functional component to this:
const SpiceHookBased = () => {
const [title, setTitle] = useState("INITIAL XYZ Title");
const changeTitle = () => {
setTitle("some value changed");
}
return (
<div>
<h1>{title}</h1>
<button className='tc ma5' onClick={changeTitle}>
</button>
);
}
EDIT: I saw on another answer you said you are using this component elsewhere which "does the final job of rendering". That is the problem, you are essentially trying to use this component as a function, which doesn't understand react hooks, and thats why the hooks aren't working. If you want to use that type of pattern you need to return a new Component with any added props that you need.

Using hooks in a higher order component

I would like to develop a new feature, which previously would have lived in a higher order component, using hooks. However, since according to the React Documentation:
"You can’t use Hooks inside of a class component, but you can definitely mix classes and function components with Hooks in a single tree."
So let's say I have some existing class component ExistingComponent that I want to extend with additional functionality, say, listening to window.resize. I would expect to do it like this.
// Existing Component
export class ExistingComponent extends React.Component {
render() {
return <div>I exist!</div>;
}
}
// Hook
export const useWindowResize = () => {
function resize() {
console.log('window resized!');
}
useEffect(() => {
window.addEventListener('resize', resize);
return function cleanup() {
window.removeEventListener('resize', resize);
};
});
};
// HOC
export const withWindowResize = component => {
useWindowResize();
return component;
};
// Extended Component
export const BetterComponent = withWindowResize(ExistingComponent);
However, this fails with Uncaught Invariant Violation: Hooks can only be called inside the body of a function component. I do use react-hot-loader, but I am still able to use hooks in component functions that don't return a class component. Also, I can remove the useWindowResize() from the function and it renders as expected.
I am also able to render the example provided in the docs, so I know it's not a problem with hooks generically:
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Is this the wrong approach?
You can return a new function component from your withWindowResize HOC in which you call the hook and spread the props on the component you pass in.
You can also pass an empty array as second argument to useEffect to only have it run once after the initial render.
const { useEffect } = React;
class ExistingComponent extends React.Component {
render() {
return <div>I exist!</div>;
}
}
const useWindowResize = () => {
useEffect(() => {
function resize() {
console.log('window resized!');
}
window.addEventListener('resize', resize);
return () => {
window.removeEventListener('resize', resize);
};
}, []);
};
const withWindowResize = Component => {
return (props) => {
useWindowResize();
return <Component {...props} />;
}
};
const BetterComponent = withWindowResize(ExistingComponent);
ReactDOM.render(<BetterComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

React - Forwarding multiple refs

I have a SideNav component that contains dynamically created links that need to scroll to a corresponding header in an adjacent html table (InfoTable). I've tried multiple different ways of accomplishing this to no avail.
export default class Parent extends Component {
state = {
categories: [],
}
scrollToHeader = (tableRefString) => {
// Function to pass to SideNav to access refs in InfoTable
this[tableRefString].scrollIntoView({ block: 'start' });
}
render() {
return (
<div>
<SideNav
categories={this.state.categories}
scrollToHeader={this.scrollToHeader} />
<InfoTable
categories={this.state.categories} />
</div>
);
}
}
export default class InfoTable extends Component {
render() {
return (
<div>
<table>
<tbody>
{this.props.categories.map(category => (
<>
// Forward the ref through InfoTableHeader to be set on the parent DOM node of each InfoTableHeader
<InfoTableHeader />
{category.inputs.map(input => <InfoTableRow />)}
</>
))}
</tbody>
</table>
</div>
);
}
}
In order to click a link on SideNav and scroll to the corresponding header on InfoTable, I believe that I need to forward refs that are dynamically created on Parent based on names in my categories array and set these refs to the DOM nodes for each header in InfoTable. From there I would pass a function to SideNav that could access the refs in Parent in order to scroll to the header.
How can I go about forwarding multiple refs at once to my InfoTable component?
Is there a cleaner way to accomplish what I'm trying to do? I've looked into React.findDOMNode() but refs seem to be the better option.
I know there is an already accepted answer, and while I find #nicholas-haley's solution acceptable.
I think a better way to go about it would be to use the built-in useImperativeHandle hook.
IMPORTANT: The React Hooks Api is available as of
react#16.8.0 and later
react-native#0.59.0 and later
The React hooks API Docs state:
useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with `forwardRef
This note is followed by the following example:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
Thus, in my opinion, a much cleaner solution would be to delegate the needed refs through the useImperativeHandle hook.
This way there is no need for a special ref syntax, and the component can simply return a specific type of a FatherRef; Example:
// LabelInput.js
function LabelInput(props, ref) {
const labelRef = useRef();
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
get input() {
return inputRef.current;
},
get label() {
return labelRef.current;
},
// ... whatever else one may need
}));
return (
<div>
<label ref={labelRef} ... />
<input ref={inputRef} ... />;
</div>
)
}
LabelInput = forwardRef(LabelInput);
function MyScreen() {
const labelInputRef = useRef();
const onClick = useCallback(
() => {
// labelInputRef.current.focus(); // works
// labelInputRef.current.input.focus(); // works
// ... etc
},
[]
);
return (
...
<LabelInput ref={labelInputRef} ... />
....
)
}
I had a similar situation where I needed multiple refs to be forwarded to the child of my Parent component.
I still have not found an elegant solution, however you might try passing your refs as an object, and destructuring within the forwardRef callback:
// Parent
ref={{
ref1: this.ref1,
ref2: this.ref2
}}
// Child
export default React.forwardRef((props, ref) => {
const { ref1, ref2 } = ref;
return (
<Child1
{...props}
ref={ref1}
/>
<Child2
{...props}
ref={ref2}
/>
);
});
I'm not a big fan of the naming here (I'd prefer ref to be called refs), but this works in case you're in a pinch.
EDIT:
In 2020 I believe #samer-murad's answer is the best solution to this problem.
I actually just picked this up from react-hook-form, but they presented a nice way to share ref and assign multiple refs at once:
<input name="firstName" ref={(e) => {
register(e) // use ref for register function
firstNameRef.current = e // and you can still assign to ref
}} />
I still don't understand what goes on in that ref property, but the following React source code typeof type.render === 'function' + the other answers led me to try passing a function of hoisted refs from parent, and it works!
class Child extends React.Component {
render() {
return (
<div>
<div
ref={this.props.pa}
style={this.props.style}
onClick={async () => {
this.storeAuth(...this.props.storableAuth);
this.props.clearStore();
}}
/>
<div
ref={this.props.fwd}
style={this.props.style}
onClick={async () => {
this.props.onStart();
const res = await this.getUserInfo(verbose, auth, storedAuth);
if (res === "login?") this.props.onPromptToLogin();
if (res) this.props.onFinish(); //res.isAnonymous
}}
/>
</div>
);
}
}
export default React.forwardRef((props, getRefs) => {
const { pa, fwd } = props.getRefs();
return <Child fwd={fwd} pa={pa} {...props} />;
});
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.pa = React.createRef();
this.fwd = React.createRef();
}
render() {
return (
<Child
getRefs={() => {
return {
pa: this.pa,
fwd: this.fwd
};
}}
storableAuth={this.state.storableAuth}//[]
clearAuth={() => this.setState({ storableAuth: null })}
/>
);
}
}
This is not exactly what the author of this question asked, but this title could feet to this question as well: how do I allow developers using my React component to pass a ref if I pass ref internally in my component also (mean, passing multiple refs that will get a ref for this element),
this is the solution i came with:
import { useState, useRef } from "react";
export default function App() {
const [, render] = useState({});
const reRender = () => render({});
const divRef = useRef();
console.log("App", divRef);
return <Component reRender={reRender} passPropsToDiv={{ ref: divRef }} />;
}
const Component = ({ passPropsToDiv, reRender }) => {
const div1Ref = useRef();
const { ref: extraRef = null, ...passPropsToDivNoRef } = passPropsToDiv ?? {};
extraRef.current = div1Ref.current;
console.log("Component", div1Ref);
return (
<div className="App">
<div ref={div1Ref} {...passPropsToDivNoRef}>
i will have a ref
</div>
<button onClick={reRender}>reRender</button>
</div>
);
};
codesandbox: https://codesandbox.io/s/react-use-pass-multiple-refs-legm7p?file=/src/App.js

React assign key to already rendered component

Is it possible?
I have a component where children are rendered by an arbitrary mapping function coming in as props. A simplified example:
class SomeComponent extends Component {
render() {
const { renderChild, businessObjects } = this.props
return <div>
{businessObjects.map(renderChild)}
</div>
}
}
I obviously get a warning saying children are rendered without the key attribute.
I tried assigning the key after the vdom element is rendered:
...
{
businessObjects.map(e => {
const vdom = renderChild(e)
vdom.key = e.id
return vdom
})
}
...
But the object returned from the JSX transform is frozen, so I can't do this. Also there is no API to temporarily unfreeze then re-freeze objects in js. Cloning is out of question for performance reasons (thousands of components are rendered like this)
What can I do?
Again, for performance reason I can't wrap the rendered children into another component, so a solution like this wouldn't work:
const Child = ({renderChild, bo}) => (<div>{renderChild(bo)}</div>)
// in SomeComponent
...
{
businessObjects.map(e => (<Child
key={e.id}
bo={e}
renderChild={renderChild}
/>)
)
}
...
Update
The reason for this structure is that SomeComponent is a dumb component, and has no access to application state (redux). But the rendered children do need to have access to dispatch (I do it in a form of connected action creators).
So you can imagine the whole thing like this:
const createChildRenderer = ({actionFoo, actionBar}) => (obj) => {
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in a connected component
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const renderChild = createChildRenderer({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
renderChild={renderChild}
businessObjects={this.props.businessObjects}>
}
}
The way I ended up solving this by taking an actual react component as an argument:
So that in the dumb component that previously took a renderer function, now I take a component:
class SomeComponent extends Component {
render() {
const { ChildComponent, businessObjects } = this.props
return <div>
{businessObjects.map((o) => (<ChildComponent
businessObject={o}
key={o.id}
/>)}
</div>
}
}
And where I previously created the renderer function, now I create the component:
const createChildComponent = ({actionFoo, actionBar}) =>
({ businessObject: obj }) => { // this is now a component created dynamically
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in the connected component:
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const ChildComponent = createChildComponent({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
ChildComponent={ChildComponent}
businessObjects={this.props.businessObjects}>
}
}
You can use cloneElement on the child received from renderChild:
React.cloneElement(
child,
{...child.props, key: yourKeyValue}
)

Resources