React Reuse Fetch JSON data throughout components - javascript

Hi there I am newbie to React and learning about reusable functions throughout my project.
I would like to fetch my JSON data but not have to call it everytime in my component.
App.js
import React, { Component } from 'react';
import { Route, NavLink, HashRouter } from "react-router-dom";
import logo from '../assets/logo.png';
import './app.css';
import About from "../about/about";
import Services from "../services/services";
import Testimonials from "../testimonials/testimonials";
import Contact from "../contact/contact";
class App extends Component {
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
render() {
return (
<HashRouter>
<div className="container">
<div className="header">
<div className="App-logo"><NavLink exact to="/"><img src={logo} alt="logo" /></NavLink></div>
<nav className="Nav-Desktop">
{this.state.items.map((item, index) => (
<div key={index}>
{
item.links.map((link, i) => (
<NavLink key={i} exact to={link.url}>{link.text}</NavLink>
))}
</div>
))}
{
this.state.items.map((item, index) => {
return <div key={index}></i><strong> {item.mainContact.phone}</strong></div>
})
}
</nav>
</div>
<main className="content">
<Route exact path="/" component={About}/>
<Route path="/services" component={Services}/>
<Route path="/testimonials" component={Testimonials}/>
<Route path="/contact" component={Contact}/>
</main>
{this.state.items.map((item, index) => {
return <footer key={index}>Β© Copyright {item.title} {(new Date().getFullYear())}</footer>
})
}
</div>
</HashRouter>
);
}
}
export default App;
I am successfully mapping my data and displaying it, but I have other files that include this snippet
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
I have tried exporting the getItems() like so in a helper.js file and importing the file import { getItems } from '../helpers/helpers'; however the code did not work properly and got stuck at Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
export function getItems() {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
If anyone can give me pointers as to the error / right way to go about this that would be helpful. Cheers

Two things you need to know when you want to reuse the data instead of calling fetch again and again
Do fetch call in top most component i.e., parent component and pass down data to all the children, children to children components but, do remember this will be hectic for you. This approach is good when you are building small application which will be like max to max 50 components. But when your application grows big this is not a recommended way of reusing the data across components.
Use Redux state management library for data reusability across components. This acts like a centralised store for your application. This is used mostly in every React app these days. With Redux you can make an action call in parent component and that action will actually fetch the data and pass it to the reducer. So now reducer will set the data in Redux store. Now the data is accessible in every component by getting the data from Redux store using state.get. So you can call redux action like this.props.getItems(); wherever you need the data in the component and the component mapStateToProps will make that data available to your component as props
How to get the data from Redux store?
Define a function mapStateToProps in component and get the data from Redux store using state.get and return it in the function. Pass the mapStateToProps to connect method. You will be connecting your component to Redux HOC component called connect. This connect method accepts actions and Redux state and make them available to the component as props.
Regarding your issue
Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
The reason you get this issue because this isn’t available inside the exported getItems function.
What you need to do to fix the issue is pass this to getItems function as a parameter
Note: Here this is a current context
import { getItems } from '../helpers/helpers';
componentDidMount(){
getItems(this);
}
helpers.js:
the below function is treated as a normal JavaScript function. This function no wr belongs the component to bind it in component constructor. But in order to play with the state of the component you need to pass the component context this. But this is old way, now a days we all use Redux to avoid these old concepts
export function getItems(this) {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
This way you can reuse this function and do the setState.
But do remember these solutions will make complex when your application grows big in future. So I would argue you to go with Redux state management library from now to avoid hectic migration in future :)
Excuse me if there are any typo errors because I am answering from my mobile.

You need to pass the context to your helper function. Considering you're still importing the function from your helper, on your componentDidMount, it can be like:
componentDidMount(){
getItems.call(this);
}

So good job with the fetching, there are a lot of paths you can take with sharing data across components, the two main ones you can look into are:
Parent state holds the data, passes it through props
A Store (Mobx, Redux) holds the data, and you inject it into your components as needed
I'm not going to give you the tutorial on how to do them all but I'm assuming the first option will be best for you right now.
If your components are using the same data, they should theoretically have the same parent component in a structure similar to this:
Page Component (Overarching component, lays out the page structure)
Header
Component 1
Component 2
Footer
So you'll do this call on the mount of Page component and pass this.state.items as a prop to the other components that need it... Stores have a similar process they just save the double handling of props :)

Since you're accessing/modifying state in your getItems() method, you should bind it in your constructor like this:
constructor() {
super(props);
this.state = {
items : []
};
this.getItems = this.getItems.bind(this);
}
or you can simply declare it with arrow function so you don't have to bind it:
getItems = () => {
// some codes here
}
Another thing, the only ones who can access/modify the state of a compoenent is its own methods. If you want to use it outside of itself, import those components inside your component as children then pass your getItems() method as prop.
render() {
return (
<HashRouter>
<div className="container">
// some other codes
<ChildComponent getItems={this.getItems} />
</div>
</HashRouter>
);
}

Related

How to access state from function below component? [duplicate]

I have 2 react components that need to share a state, react-router shows component A, which takes some inputs and adds it to its state, after the state has been successfully updated, I want to redirect to component B, where the user adds some more inputs and updates the same state as component A to build an object with inputs from A and B before I submit a post request to my api to save the data from both component A and B. How can I accomplish this, is there a way to use react-router, or do I have to set up a parent/child relationship between the components?
The dependency type between the components will define the best approach.
For instance, redux is a great option if you plan to have a central store. However other approaches are possible:
Parent to Child
Props
Instance Methods
Child to Parent
Callback Functions
Event Bubbling
Sibling to Sibling
Parent Component
Any to Any
Observer Pattern
Global Variables
Context
Please find more detailed information about each of the approaches here
What you want is to implement some object that stores your state, that can be modified using callback functions. You can then pass these functions to your React components.
For instance, you could create a store:
function Store(initialState = {}) {
this.state = initialState;
}
Store.prototype.mergeState = function(partialState) {
Object.assign(this.state, partialState);
};
var myStore = new Store();
ReactDOM.render(
<FirstComponent mergeState={myStore.mergeState.bind(myStore)} />,
firstElement
);
ReactDOM.render(
<SecondComponent mergeState={myStore.mergeState.bind(myStore)} />,
secondElement
);
Now, both the FirstComponent and SecondComponent instances can call this.props.mergeState({ . . .}) to assign state to the same store.
I leave Store.prototype.getState as an exercise for the reader.
Note that you can always pass the store (myStore) itself to the components; it just feels less react-y to do so.
Here is some more documentation that might be of interest:
React Docs: "Communicate Between Components"
For communication between two components that don't have a
parent-child relationship, you can set up your own global event
system. Subscribe to events in componentDidMount(), unsubscribe in
componentWillUnmount(), and call setState() when you receive an event.
Flux pattern is one of the possible ways to arrange this.
The easiest way to use a shared state between several components without rewriting your application's code to some state management system is use-between hook.
Try this example in codesandbox
import React, { useState } from "react";
import { useBetween } from "use-between";
// Make a custom hook with your future shared state
const useFormState = () => {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
return {
username, setUsername, email, setEmail
};
};
// Make a custom hook for sharing your form state between any components
const useSharedFormState = () => useBetween(useFormState);
const ComponentA = () => {
// Use the shared hook!
const { username, setUsername } = useSharedFormState();
return (
<p>
Username: <input value={username} onChange={(ev) => setUsername(ev.target.value)} />
</p>
);
};
const ComponentB = () => {
// Use the shared hook!
const { email, setEmail } = useSharedFormState();
return (
<p>
Email: <input value={email} onChange={(ev) => setEmail(ev.target.value)} />
</p>
);
};
const ComponentC = () => {
// Use shared hook!
const { email, username } = useSharedFormState();
return (
<p>
Username: {username} <br />
Email: {email}
</p>
);
};
export const App = () => (
<>
<ComponentA />
<ComponentB />
<ComponentC />
</>
);
For first, we create useFormState custom hook as a source for our state.
In the next step, we create useSharedFormState hook who uses useBetween hook inside. That hook can be used in any component who can read or update the shared state!
And the last step is using useSharedFormState in our components.
useBetween is a way to call any hook. But so that the state will not be stored in the React component. For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.
I'll be going straight to hell for this:
// src/hooks/useMessagePipe.ts
import { useReducer } from 'react'
let message = undefined
export default function useMessagePipe(): { message: string | undefined, sendMessage: (filter: string) => void } {
const triggerRender = useReducer((bool) => !bool, true)[1]
function update(term: string) {
message = message.length > 0 ? message : undefined
triggerRender()
}
return { message: message, sendMessage: update }
}
You can then use this in any component anywhere in your applications' component hierarchy to send a message:
// src/components/ExampleInputToHell.jsx:
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleInputToHell() = () => {
const { sendMessage } = useMessagePipe()
return <input onChange={(e) => sendMessage('πŸ”₯ Hell-O 😈: ' + e.target.value)} />
}
… and consume the message any component anywhere in your applications' component hierarchy:
// src/components/ExampleOutputInHell.jsx
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleOutputInHell() {
const { message } = useMessagePipe()
return <p>{message}</p>
}
Explanation
let message outside the useMessagePipe-closure holds a global state, that (as far is the theory goes) gets surrounded in it's own module scope
as react's functional component logic will know nothing about that state, triggerRender – a version of a dirty hack that's actually mentioned on the React FAQ – needs to be applied to signal to react that all components consuming this function are asked to re-evaluate (re-render).
Disclaimer
This is a global state, meaning: all components using useMessagePipe see the same message and access the same update function, application-wide. If you want to have a new "channel" between two other components, you need to create another hook referring to another global state holder outside the closure (like message in this example).
If you know any better and have the time and resources, you probably don't want to go down this muddy road to perdition and instead learn how to properly useContext or (an easier way) give useBetween by #Slava Birch a star.
But if you just want a quick and dirty solution to pipe a piece of data between components right now … well this ~10 lines of code made my day for a simple task at hand and worked flawless so far. However my gut feeling says something is going to break if used for important things, hence any additions & theories on the conditions under which it will break are highly welcome.
Either you can set up a parent child relationship then you can pass data to child components as props.
Else, if you want to create interaction between 2 components which are not related to either(parent/child) you can either check out flux or even better redux.
I would say you should go with redux.See Here why
You can build custom React hooks to share a state between components, I made one here. You can use it by downloading use-linked-state.js file.
After importing useStateGateway hook, declare a gateway in parent component and pass it down to your child components
import {useStateGateway} from "use-linked-state";
const myGateway = useStateGateway({partA:null, partB:null});
return (
<>
<ComponentA gateway={myGateway}>
<ComponentB gateway={myGateway}>
<ComponentPost gateWay={myGateway}>
</>
)
Then you have access shared state between those three components by a custom useLinkedState hook
import { useLinkedState } from "use-linked-state";
export default function ComponentA({gateway}){
const [state, setState] = useLinkedState(gateway);
<your logic>
}
In your logic ComponentA and ComponentB would be responsible for their part in shared object {partA:"filled by ComponentA", partB:"filled by componentB"}.
Finally ComponentPost post the result if partA and partB of shared object were valid.
In this way you can compose components and make connection between them to talk to each other.

How and where to pass JSON data as a prop when creating components

I am getting some Club information from a JSON I want to use in my React component 'Club'. I created a component ClubList in which all Club components with their corresponding name should be created but I don't know where I should make the HTTP request and where to save it, so I can use it in the return statement.
I tried saving all titles in an array but I stopped at the point I had to pass the titles to each Club element. I just started working with ReactJS so I am a basically complete beginner in ReactJS (Not in JS though).
This is the ClubList class
class ClubList extends React.Component {
render() {
return (
<div className="Clublist">
<Club title="Club1" />
<Club title="Club2" />
...
...
...
</div>
)
}
}
And that is the Club class
class Club extends React.Component {
clubProp = {...}
render() {
return (
<div className="Club">
<div className="image-container">
<img src={this.clubProp.imageSrc} width="300px" height="300px"/>
</div>
<h2>{this.clubProp.name}</h2>
<div className="Business-information">
<div className="Business-address">
<p>{this.clubProp.address}</p>
<p>{this.clubProp.city}</p>
<p>{this.clubProp.zipCode}</p>
</div>
<div className="Business-reviews">
<h3>{this.clubProp.category}</h3>
<h3 className="rating">{this.clubProp.rating}</h3>
<p>90 reviews</p>
</div>
</div>
</div>
)
}
}
I will use an API to get the Club-names but I don't know how I can organize the variables to be accessible in the right places since I don't quite grasp how the scopes in React work. I already have the code for getting the JSON ready, just need to know where to put it and how to pass the values in
Your basic syntax would look like the following. Your component will maintain clubs using component state. In the componentDidMount lifecycle function, you can make your api call and then store the results in your component's state. Any time you call setState, your component will re-render.
class ClubList extends React.Component {
state = {
clubs: []
};
componentDidMount = async () => {
const clubs = await this.fetchClubs();
this.setState({ clubs });
}
render() {
const { clubs } = this.state;
return (
<div className="Clublist">
{clubs.map(club => (
<Club title={club.title} />
)}
</div>
);
}
}
Eventually you can pull out state management from all of your components and use something like redux or MobX and let your components focus solely on rendering html.
Where you should make the API request?
Ideally, we use redux-sagas or redux-thunk as middleware while making API requests. However, since, you're just getting started, you could make the API call in the componentDidMount lifecycle method of your ClubList component.
Now, I am assuming that you receive an array of Clubs. You could map over this array and render each Club component.
Where you should store this data?
Common practice is to use a state-management library like redux with react. It helps scale and maintain your app better. However, you could also use the state of the ClubList component to store the data of your API call.
I hope this was helpful.
From the docs, you make API calls in the componentDidMount life-cycle method. I'd recommend looking at the docs for examples:
https://reactjs.org/docs/faq-ajax.html
The docs use the browser's fetch method to make the request, but I'd personally recommend using axios. https://www.npmjs.com/package/axios, since it's a bit more straight forward.
React uses state, which can be passed down through the component tree. As you are using class components, a typical way of getting your JSON data into the app's state would be to fetch the data in the componentDidMount() lifecyle method of your top level component, and run this.setState({clubProp: result.data}) in the fetch/axios callback. You can pass it to children where they are available as props.
I would argue that Redux is overkill - and that it would be better to defer learning it until you have a state management problem. The new hooks implementation and context API will also change best practices for state management. The guy who created Redux says "Flux libraries are like glasses: you’ll know when you need them."
// Here is the simplified example:
class ClubList extends React.Component {
constructor(props) {
super(props);
this.state = {
ClubDetails: [],
};
}
componentDidMount() {
fetch("api link put here in quotes")
.then(results => {
return results.json();
}).then(data => {
let ClubDetails = data;
this.setState({ ClubDetails: ClubDetails });
})
}
render() {
const ClubDetails = this.state.ClubDetails;
const listItems = ClubDetails.map((clubProp,index) =>
<Club key={index} clubProp={clubProp}/>
);
return (
{listItems}
);
}
}
class Club extends React.Component {
render() {
return (
<div className="Club">
<div className="image-container">
<img src={this.clubProp.imageSrc} width="300px" height="300px"/>
</div>........
My recommendation is using fetch middleware in redux to control all datas in props(You can find in redux github examples), besides the responsive data, you can also monitor fetching status across components.

Why aren't my props available to use in my component when I need them?

I keep running into similar issues like this, so I must not fully understand the lifecycle of React. I've read a lot about it, but still, can't quite figure out the workflow in my own examples.
I am trying to use props in a child component, but when I reference them using this.props.item, I get an issue that the props are undefined. However, if the app loads and then I use the React Browser tools, I can see that my component did in fact get the props.
I've tried using componentDidMount and shouldComponentUpdate in order to receive the props, but I still can't seem to use props. It always just says undefined.
Is there something I'm missing in React that will allow me to better use props/state in child components? Here's my code to better illustrate the issue:
class Dashboard extends Component {
state = { reviews: [] }
componentDidMount () {
let url = 'example.com'
axios.get(url)
.then(res => {
this.setState({reviews: res.data })
})
}
render() {
return(
<div>
<TopReviews reviews={this.state.reviews} />
</div>
);
}
}
export default Dashboard;
And then my TopReviews component:
class TopReviews extends Component {
state = { sortedReviews: []}
componentDidMount = () => {
if (this.props.reviews.length > 0) {
this.sortArr(this.props.reviews)
} else {
return <Loader />
}
}
sortArr = (reviews) => {
let sortedReviews = reviews.sort(function(a,b){return b-a});
this.setState({sortedReviews})
}
render() {
return (
<div>
{console.log(this.state.sortedReviews)}
</div>
);
}
}
export default TopReviews;
I'm wanting my console.log to output the sortedReviews state, but it can never actually setState because props are undefined at that point in my code. However, props are there after everything loads.
Obviously I'm new to React, so any guidance is appreciated. Thanks!
React renders your component multiple times. So you probably see an error when it is rendered first and the props aren't filled yet. Then it re-renders once they are there.
The easy fix for this would be to conditionally render the content, like
<div>
{ this.props.something ? { this.props.something} : null }
</div>
I would also try and avoid tapping into the react lifecycle callbacks. You can always sort before render, like <div>{this.props.something ? sort(this.props.something) : null}</div>
componentDidMount is also very early, try componentDidUpdate. But even there, make your that your props are present.
For reference: see react's component documentation

How to make a shared state between two react components?

I have 2 react components that need to share a state, react-router shows component A, which takes some inputs and adds it to its state, after the state has been successfully updated, I want to redirect to component B, where the user adds some more inputs and updates the same state as component A to build an object with inputs from A and B before I submit a post request to my api to save the data from both component A and B. How can I accomplish this, is there a way to use react-router, or do I have to set up a parent/child relationship between the components?
The dependency type between the components will define the best approach.
For instance, redux is a great option if you plan to have a central store. However other approaches are possible:
Parent to Child
Props
Instance Methods
Child to Parent
Callback Functions
Event Bubbling
Sibling to Sibling
Parent Component
Any to Any
Observer Pattern
Global Variables
Context
Please find more detailed information about each of the approaches here
What you want is to implement some object that stores your state, that can be modified using callback functions. You can then pass these functions to your React components.
For instance, you could create a store:
function Store(initialState = {}) {
this.state = initialState;
}
Store.prototype.mergeState = function(partialState) {
Object.assign(this.state, partialState);
};
var myStore = new Store();
ReactDOM.render(
<FirstComponent mergeState={myStore.mergeState.bind(myStore)} />,
firstElement
);
ReactDOM.render(
<SecondComponent mergeState={myStore.mergeState.bind(myStore)} />,
secondElement
);
Now, both the FirstComponent and SecondComponent instances can call this.props.mergeState({ . . .}) to assign state to the same store.
I leave Store.prototype.getState as an exercise for the reader.
Note that you can always pass the store (myStore) itself to the components; it just feels less react-y to do so.
Here is some more documentation that might be of interest:
React Docs: "Communicate Between Components"
For communication between two components that don't have a
parent-child relationship, you can set up your own global event
system. Subscribe to events in componentDidMount(), unsubscribe in
componentWillUnmount(), and call setState() when you receive an event.
Flux pattern is one of the possible ways to arrange this.
The easiest way to use a shared state between several components without rewriting your application's code to some state management system is use-between hook.
Try this example in codesandbox
import React, { useState } from "react";
import { useBetween } from "use-between";
// Make a custom hook with your future shared state
const useFormState = () => {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
return {
username, setUsername, email, setEmail
};
};
// Make a custom hook for sharing your form state between any components
const useSharedFormState = () => useBetween(useFormState);
const ComponentA = () => {
// Use the shared hook!
const { username, setUsername } = useSharedFormState();
return (
<p>
Username: <input value={username} onChange={(ev) => setUsername(ev.target.value)} />
</p>
);
};
const ComponentB = () => {
// Use the shared hook!
const { email, setEmail } = useSharedFormState();
return (
<p>
Email: <input value={email} onChange={(ev) => setEmail(ev.target.value)} />
</p>
);
};
const ComponentC = () => {
// Use shared hook!
const { email, username } = useSharedFormState();
return (
<p>
Username: {username} <br />
Email: {email}
</p>
);
};
export const App = () => (
<>
<ComponentA />
<ComponentB />
<ComponentC />
</>
);
For first, we create useFormState custom hook as a source for our state.
In the next step, we create useSharedFormState hook who uses useBetween hook inside. That hook can be used in any component who can read or update the shared state!
And the last step is using useSharedFormState in our components.
useBetween is a way to call any hook. But so that the state will not be stored in the React component. For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.
I'll be going straight to hell for this:
// src/hooks/useMessagePipe.ts
import { useReducer } from 'react'
let message = undefined
export default function useMessagePipe(): { message: string | undefined, sendMessage: (filter: string) => void } {
const triggerRender = useReducer((bool) => !bool, true)[1]
function update(term: string) {
message = message.length > 0 ? message : undefined
triggerRender()
}
return { message: message, sendMessage: update }
}
You can then use this in any component anywhere in your applications' component hierarchy to send a message:
// src/components/ExampleInputToHell.jsx:
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleInputToHell() = () => {
const { sendMessage } = useMessagePipe()
return <input onChange={(e) => sendMessage('πŸ”₯ Hell-O 😈: ' + e.target.value)} />
}
… and consume the message any component anywhere in your applications' component hierarchy:
// src/components/ExampleOutputInHell.jsx
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleOutputInHell() {
const { message } = useMessagePipe()
return <p>{message}</p>
}
Explanation
let message outside the useMessagePipe-closure holds a global state, that (as far is the theory goes) gets surrounded in it's own module scope
as react's functional component logic will know nothing about that state, triggerRender – a version of a dirty hack that's actually mentioned on the React FAQ – needs to be applied to signal to react that all components consuming this function are asked to re-evaluate (re-render).
Disclaimer
This is a global state, meaning: all components using useMessagePipe see the same message and access the same update function, application-wide. If you want to have a new "channel" between two other components, you need to create another hook referring to another global state holder outside the closure (like message in this example).
If you know any better and have the time and resources, you probably don't want to go down this muddy road to perdition and instead learn how to properly useContext or (an easier way) give useBetween by #Slava Birch a star.
But if you just want a quick and dirty solution to pipe a piece of data between components right now … well this ~10 lines of code made my day for a simple task at hand and worked flawless so far. However my gut feeling says something is going to break if used for important things, hence any additions & theories on the conditions under which it will break are highly welcome.
Either you can set up a parent child relationship then you can pass data to child components as props.
Else, if you want to create interaction between 2 components which are not related to either(parent/child) you can either check out flux or even better redux.
I would say you should go with redux.See Here why
You can build custom React hooks to share a state between components, I made one here. You can use it by downloading use-linked-state.js file.
After importing useStateGateway hook, declare a gateway in parent component and pass it down to your child components
import {useStateGateway} from "use-linked-state";
const myGateway = useStateGateway({partA:null, partB:null});
return (
<>
<ComponentA gateway={myGateway}>
<ComponentB gateway={myGateway}>
<ComponentPost gateWay={myGateway}>
</>
)
Then you have access shared state between those three components by a custom useLinkedState hook
import { useLinkedState } from "use-linked-state";
export default function ComponentA({gateway}){
const [state, setState] = useLinkedState(gateway);
<your logic>
}
In your logic ComponentA and ComponentB would be responsible for their part in shared object {partA:"filled by ComponentA", partB:"filled by componentB"}.
Finally ComponentPost post the result if partA and partB of shared object were valid.
In this way you can compose components and make connection between them to talk to each other.

ReactJs - Reuseable Redux Service

I understand the concept of Redux's actions, reducers, and mapping to stores.
I have been able to successfully execute Redux into my app.
I was going along merrily using React's contextTypes for child components that needed data from Redux that had been called before.
Then I ran into a strange situation where the data was mutated by a child. When I posted the problem on SO, a member told me I should be using contextTypes sparingly anyway.
So the only way to overcome my problem was map to stores, AGAIN, in the child's parent, like a higher component of the parent had done earlier, and pass that data to the child as props.
But that seems all wrong to me. Mapping to the same store again? Why? What am I not understanding? Why do I have to write this on every component that needs the same data another component mapped to?
export default class Foo extends Component {
.....
// I DID THIS STUFF IN A HIGHER COMPONENT.
// WHY MUST I REPEAT MYSELF AGAIN?
// WHAT AM I NOT UNDERSTANDING?
static propTypes = {
children: PropTypes.node.isRequired,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchProductsIfNeeded());
}
.....
}
const mapStateToProps = (state) => {
const {productsReducer } = state;
if (!productsReducer) {
return {
isFetching: false,
didInvalidate: false,
error: null,
products: []
};
}
return {
error: productsReducer.error,
isFetching: productsReducer.isFetching,
didInvalidate: productsReducer.didInvalidate,
products: productsReducer.products
};
};
export default connect(mapStateToProps)(Foo);
I looked at containers, but it appears to me that containers wrap all dumb components in them at once as such ...
<ProductsContainer>
<ProductsComponent />
<ProductSpecialsComponent />
<ProductsDiscountedComponent />
</ProductsContainer>
And that is not what I want. I thought, like a service I could use that container in each respective dumb component as a such ....
<ProductsContainer>
<ProductsDiscountedComponent />
</ProductsContainer>
<ProductsContainer>
<ProductSpecialsComponent />
</ProductsContainer>
<ProductsContainer>
<ProductsComponent />
</ProductsContainer>
Right now in order to get my 3 sub components illustrated above, each one of them has to map to stores and that just seems all wrong.
I cannot find anything that I can grasp as a solution.
Question:
Is there a way I can map to a particular store just once, and call on that "service" for those components that need that data?
If so, examples would be appreciated.
Post Script:
I though perhaps if I could perform the 'mapping service' as a pure JavaScript function o/s of react, and just import that function in the components that need it, that would solve the problem, but I have not seen any examples of Redux stores being mapped o/s React.
UPDATE:
I posted the solution here ......
React-Redux - Reuseable Container/Connector
First, an aside about your past problems. It's true that context is not appropriate for something like this. You should also be worried about the mutation you mentioned. If you're using a Redux store, the data that exits it should always be immutable. Perhaps a library like Immutable.js would help there.
Now let's turn to the matter at hand. Perhaps what you aren't fully grokking is what a "dumb" component is. A dumb component should be stateless and a pure:
const Product = ({ name, comments }) => (
<div>
<h1>{name}</h1>
<CommentsList comments={comments} />
</div>
);
The component gets everything it needs from props. Now there are a number of ways to get data into this component, but they are all based on props. For example, the following is the most straightforward:
const ProductList = ({ products }) => (
<div>
{products.map( p => <Product product={product} /> )}
</div>
);
class App extends Component {
getInitialState () {
return { products: [] };
}
componentDidMount () {
// connect to store, blah blah...
}
render () {
return (
<div>
{/* blah blah */}
<ProductsList products={this.state.products} />
{/* blah blah */}
</div>
);
}
}
As you can see from the example, the entire components tree will get its state from props that are simple passed down from one connection to the store. Aside from App, all components are dumb, stateless, and predictable.
But there are also cases where connecting the entire tree through props is impractical and where we need localized connections to our stores. That's where HOCs can be hugely helpful:
const WithProducts = Comp => class WrappedComponent extends Component {
getInitialState () {
return { products: [] };
}
componentDidMount () {
// connect to store, blah blah...
}
render () {
return (
<Comp products={this.state.products} {...this.props} />
);
}
}
const ProductListWithProducts = WithProducts( ProductList );
Now any component we so wrap will receive the list of products from the store as a prop - no code duplication required. No repeating yourself. Notice how I did not alter the ProductList or Product components to make this work: those components are too dumb to care.
The majority of the components in any React app you create should be so dumb.
As another aside, you should not be worried about calling your store more than once. If you are worried about that, there's something wrong with the store implementation because calls to stores should be idempotent. You can use actions and so forth to populate the stores, but that should be wholly independent from getting values from stores. There should be no performance or network penalty form well-design store retrievals (and, again, using libraries like Immutable can help here too).

Categories

Resources