I'm quite new with reactjs, and I'm struggling with the following:
I have a Header component (in my App.js file) which contains an AudioPlayer. This AudioPlayer needs to be updated with current value of src (with the path of the song, stored in local) and title. Now, think about something like this:
App.js
function App(){
<Header/>
<Page1/>
<Page2/>
<Page3/>
...
<SomePage/>
...
<Footer/>
}
Header.js
import HeaderLogic from './HeaderLogic'
funtion Header(){
const {property1, property2, ..., path, title} = HeaderLogic()
//In HeaderLogic I want to get path and title, set in SomePageLogic.js
...
return <>
<AudioPlayer
src={path}
title={title}
/>
...
</>
}
SomePage.js
import SomePageLogic from './SomePageLogic'
import songPath from './somePath/songPath'
function SomePage(){
const title = 'songTitle'
const {property1, property2, ..., propertyN} = SomePageLogic(songPath, title)
//In SomePageLogic (or maybe here) I want to set path and title of the song,
//in order to be used from the HeaderLogic component
return <>
...
</>
}
I'm not sure useContext could be fine in this case, since I want to share data between unrelated components... Of course, the Header should rerender when it detects the song has been set. Should I use some kind of subscription pattern?
Any hint?
Thank you very much!
you can use Redux for storing your states. Basically React Redux is kind of a store where you can store your state and whenever it get updated it directly render html. you can access store state anywhere by using useSelector in hook, it get updated value whenever it changed by some other component
No need for any subscription pattern and definitely not using localStorage - context is exactly used for your usecase: sharing state between unrelated components.
So for instance, I want to share selected menu between the header and the Page1 somewhere below, you'd create a context with createContext(), export it's provider, wrap the children, and then you share that state between all components you set as children. Example:
const defaultSelectedMenu = 'a';
export const MenuContext = createContext({ selectedMenu: defaultSelectedMenu });
export const MenuProvider = ({ children }) => {
const [selectedMenu, setSelectedMenu] = useState(defaultSelectedMenu);
return (<MenuContext.Provider value={selectedMenu, setSelectedMenu}>
{children}</MenuContext.Provider>)
}
And then in your app.js:
...
<MenuProvider>
<Header />
<Page1 />
</MenuProvider>
<Page2 />
...
Now, your Header and Page1 components can access the MenuContext through useContext(MenuContext) and have access to the state and the mutator.
This was just a simple example but the same logic applies to any logic you want to share, heavy computation, API calls, you name it. You define the implementation in the <Provider> and then consume the implementation by accessing it through useContext
You can use localStorage.
Basically, it'll store your desired value in the browser. So, you can access it from anywhere within the application.
Related
I have a Weather project where I have two components. One for day/night timings: SunriseSunset and another for daily forecast: DailyForecast. I need to pass value of time obtained from SunriseSunset to DailyForecast.
Here are the two components for reference:
SunriseSunset.js (First File) --> Function Component
const SunriseSunset = (props) => {
const time2 = moment.tz(props.timezone).format('HH:mm')
// I want to pass the time2 value in DailyForecast.js file
return (
<React.Fragment>
</React.Fragment>
)
}
export default SunriseSunset
DailyForecast.js (Second File) --> Class Component
export class DailyForecast extends Component {
return (
<div>
</div>
)
}
}
export default DailyForecast
You should lift the shared state/data up to common ancestor. Here is a working
CodeSandbox example.
const Parent = () => {
const timezone = "Asia/Calcutta";
const time2 = moment.tz(timezone).format("HH:mm");
return (
<>
<SunriseSunset time={time2} />
<br />
<DailyForecast time={time2} />
</>
);
}
Here is the official documentation: Lifting State Up
Also, if you don't want to drill down props on multiple levels then you should consider React context API or something like Redux for managing application state. Redux is a robust state container but it might be overkill for your use case.
The first question you need to ask yourself is do these two child components have the same parent?
you need to pass values from SunriseSunset to the same parent of DailyForecast and then when the parent gets the value it needs to pass the value down to DailyForecast;(you can also use context api to do it in a simpler way)
You can use third-party state management library like redux to construct a isolated state.
And then you need to make SunriseSunset and DailyForecast have access to this isolated state. after that these child components are able to share states;(there are so many state management libraries like Mobx, or state-machine)
There are also other options if you do not want any of the above solutions.
Just use local storage or url to share your states.
For instance, in SunriseSunset you can save this state in localstorage or url and in DailyForecast you can read this state from the current url or localstorage. But this option is not elegant.
I am trying to pass data from a JSON file to a component with useContext:
import sessionData from '../session.json';
import React from 'react';
export const SessionContext = React.createContext(sessionData);
export const SessionProvider = SessionContext.Provider;
export const SessionConsumer = SessionContext.Consumer;
console.log('session data', sessionData); //console log ok
Next I am passing it into a class component
static contextType = SessionContext;
componentDidMount() {
const sessId = this.context;
console.log('steps', sessId); // log ok
}
render() {
return <SessionProvider value={sessId}> //value returns undefined.
<Child A />
<Child B />
<Child C />
</SessionProvider>;
}
In the first two code blocks shown, my data returns fine. Now I want to pass it to the child components (which are functional component). However, when I try to pass a value to the session provider <SessionProvider value={sessId}, the value returns as undefined.
I've seen other examples where the value is passed directly into the component like ``{sessId.name}. However, if I'm just trying to make sessId``` available to all the child components.
sessId doesn't exist
The error that you're getting is likely that sessId doesn't exist.
sessId exists in the componentDidMount method but not in your render method.
You would need to create that variable in your render method with this.context, just like you did in componentDidMount.
You don't need the provider
But... you don't need the provider there. When you provide an initial value to React.createContext, then that becomes a default value, so you can just consume it with the children and the data will be there.
You don't need context
However, if you're just using static JSON, then you don't need context here at all: just import the JSON file into each of the children and use it there.
Summary:
1) Do you know how to keep the state of a Context Provider present when it is mounted/unmounted through routing?
2) Or do you know a well maintained Flux implementation that supports multiple separated stores?
In detail:
Besides React components own state I've been using mostly redux so far. Besides not loving the idea of having every state managed globally, even though it might only be relevant for a subtree, it also becomes an issue for my new project.
We want to dynamically load components and add them via routing to the app. To be able to have components ready for plug and play, we want them to take care of their own state (store it, request it from the server, provide a strategy to modify it).
I read about how to dynamically add reducers to the global store with redux, but I actually find the approach of Reacts Context API much nicer where I can encapsulate some state in a Provider and can consume it wherever I need it.
The only issue I have is, that a Provider and a Consumer are React components, so if they are part of a component, that is mounted and unmounted through routing, the state that might have been created or fetched once, is gone.
It seems to me that there is no way to solve that, besides temporarily storing the state in the localstorage or on the server. If there is, please let me know!!!
If there shouldn't be a better solution:
I also thought about a more original Flux implementation that would allow multiple stores, which could be encapsulated with the relavant component subtree. But I haven't really found any well maintained Flux implementation besides Redux. Mobx being the exception, but I really prefer the reducer solution of Redux over the observable solution of Mobx. So again, if you know a multi store Flux implementation that is well maintained, please let me know!!!
I would be really happy about some feedback and hope you can point me into a direction that is more satisfiying than dynamic reducer Redux or temporarily persisted Context state.
Thanks a lot in advance!
Sorry that it's quite a late answer
Are you using React Router?
The state should be persisted and it shouldn't clear if you are navigating correctly. There should be no page reload as this will cause the state to clear.
Here is an example:
import { Router as RootRouter } from 'react-router-dom';
import Router from './routes/Router';
const App = () => {
return (
<MyContext value={useReducer(myReducer, initialState)}>
<RootRouter history={browserHistory}>
<Router />
</RootRouter>
</AuthContext>
);
}
import About from '../components/About';
const Router = () => {
return (
<Switch>
<Route exact path='/about' component={About}></Route>
</Switch>
}
On your main home component, you have to use a Link or Navlink to "switch" between components. Therefore, you'll have something like...
import { Link } from 'react-router-dom';
<Link to="/about">About</Link>
This will navigate you to the about page in which you can still access the context stage where nothing is cleared.
So I figured out a way to work around the problem with Context (first question): I store the state of the Provider component in a variable. That way, when that component is mounted again, it uses the "persisted" state as the initial value for it's state.
let persistedState = {};
const Context = React.createContext();
export class Provider extends React.PureComponent {
state = { ...persistedState };
updateState = (nextState) => this.setState(nextState, () => {
persistedState = {...this.state};
});
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
I have a React app for University Management.
Below is my router:
<Route path="selecting/" component={SelectUniversity}>
<Route path="myUniversity" component={MyUniversity} />
<Route path="studentdetails" component={AddStudentDetails} />
<Route path="payment" component={Payment} />
</Route>
the flow is==>MyUniversity==>AddStudentDetails==>Payment
As per this, everything is working as expected
All the three components MyUniversity, AddStudentDetails, Payment are extensivly using redux store
MyUniversity's mapStateToProps is as follows
function mapStateToProps(state) {
const { results } = state.results
const studentData = state.results.studentData
return {
results,
studentData,
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ ...Actions, ...studentActions }, dispatch)
}
there are many store variables involved, this is for sample purpose
Similarly, separate mapstaetoprops and mapdispatchtoprops for other two component.
Now the requirement is (for some unavoidable reasons):-
if a user directly lands on myuniversity page with a id like this below:-
http://mywebsite.com/myuniversity/9999,
I need to get the data associated with 9999 (which am already getting) and execute the same flow.
my updated router
<Route path="selecting/" component={SelectUniversity}>
<Route path="myUniversity" component={MyUniversity} />
<Route path="myUniversity/:unidetails" component={MyUniversity} />
<Route path="studentdetails" component={AddStudentDetails} />
<Route path="payment" component={Payment} />
</Route>
Once I get the data how can I update the redux store so that the existing flow will work as expected.
I know I can dispatch as many actions as i want once we get the data from ajax call but like I said there are 15-20 different state variables are involved for each of the three component. So, it does not seem like a scalable approach to fire so many dispatchers on each component load.
Approach 2
So I came up with another approach:-
1. Define a new reducer.
2. Upon getting data store the entire ajax result in your desired format in the state.
3. Now go to the mapstatetoprops of each of the three components and add conditions on every every prop level whether get data from pevious reducer or current reducer.
for example:-
lets say i have added another reducer called universitydetails
then my mapstatetoprops will look something like this
const { results } = state.results || state.universitydetails
const studentData = state.results.studentData || state.universitydetails.studentData
return {
results,
studentData,
}
then again, adding condition at each prop level (given that i am using 15-20 different state variables in each of the three components)
Approach 3:-
add redux-saga and dispatch action on myuniversity load and yield other actions based on it
But this wont be generic. Incase, I want to add simliar feature for other things such as hostel then again i need to add sagas for this hostlels and need to dispatch action on initial load.
What I think will be best approach (correct me if am wrong) :-
Define a new reducer and somehow make my component listens to this reducer(like approach 2 but without uglifying the code) so that in case i want to add another feature like hostelselector, i just need to update my new reducer structure and make my component listen to this new reducer
I am stuck at somehow
Any suggestion how to go about this?
OK I think I understood how's the flow of your application and that's my idea.
You can create one reducer that will respond to just one action called 'SET_SOURCE'. There it will put the name of the current source where you should extract your data from. Ideally it should be the name of the reducer/object where your data will be hold.
After that you have to create a reducer for each source. Every source will be responsible of itself and they won't interact each other. That means that when your ajax call will be finished and you will have to save your data inside inside the store (aka firing an action), you will fire the action to trigger the reducer that you want.
The structure of your reducers could be like this:
=> myUniversity
=> currentSource
=> sources
=> uniDetails
=> hostel etc.
You can achieve this kind of structure using combineReducers function
// myUniversityReducer.js
import uniDetails from 'uniDetailsReducer'
import hostel from 'hostelReducer'
import currentSource from 'currentSourceReducer'
const sources = combineReducers({hostel, uniDetails})
const myUniversity = combineReducers({currentSource, sources})
export myUniversity
Inside your mapStateToProps you could something like to select the current source:
function mapDispatchToProps = (state) => {
const currentSource = selectCurrentSource(state) // currentSource = 'uniDetails' => name of the reducer
const dataFromSource = selectDataFromSources(state, currentSource) // dataFromSource = state[currentSource] => object inside uniDetails
// ... if here you need to manipulate data because the structure of every
// source is different, you can have your function that will do that based on the source name
return { ...dataFromSource }
}
That's my idea but there might be the chance that I missed something or I misunderstood some of the scenario
Say I have a top most smart component called Forecast that looks like this:
function mapStateToProps(state) {
return {
dates: state.getIn(['forecast', 'dates']),
isFetching: state.getIn(['forecast', 'isFetching'])
};
}
export default connect(mapStateToProps, {
fetchForecast
})(Forecast));
Which wraps a Forecast component like this:
import { getSummary, getDayForecast } from '../selectors/selectors';
export default class Forecast extends Component {
render() {
const { dates, isFetching } = this.props;
return (
<div className="row">
{dates.map(date => (
<Weather
key={date}
date={date}
getSummary={getSummary}
getDayForecast={getDayForecast}
/>
))}
</div>
);
}
};
Here I am passing 2 selectors as props into a Weather component. The selectors look like this:
import { createSelector } from 'reselect';
import moment from 'moment';
import { fromJS } from 'immutable';
const getDay = (state, key) => state.getIn(['forecast', 'forecast']).find(x => x.get('id') === key);
export const getSummary = createSelector(
[getDay],
(day => {
const firstPeriod = day.get('periods').first();
return fromJS({
date: day.get('date'),
outlook: firstPeriod.get('outlook'),
icon: firstPeriod.get('icon')
});
})
);
export const getDayForecast = createSelector(
[getDay],
(day) => day.get('periods').map(period => fromJS({id: period.get('id') }))
);
I don't have to pass these selectors down as props, I could easily just reference them in the weather component but I am confused as to how I would use these selectors in the Weather component as the Weather component is also dumb and won't have any reference to state. I only want 1 container or smart component at the top which the child components call or get props passed down.
The only way I can see of making this work is to have an intermediatary WeatherContainer component that looks something like this:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import Weather from '../components/Weather';
import { getSummary, getDayForecast } from '../selectors/selectors';
function mapStateToProps(state, ownProps) {
return {
summary: getSummary(state, ownProps.date),
detail: getDayForecast(state, ownProps.date)
};
}
export default(connect(mapStateToProps,{}))(Weather);
And I would call like this:
{dates.map(date => (
<WeatherContainer
key={date}
date={date}
getSummary={getSummary}
getDayForecast={getDayForecast}
/>
))}
This seems completely wrong to have to create a container component like this.
How can I make use of selectors in dumb components or how can I pass them down as props baring in mind that they also need reference to the state?
In your WeatherContainer mapStateToProps you use your selectors but you're still passing them down as props. This is not necessary.
Besides that, you should know that creating your container WeatherContainer is the right way to go about things. You should never give a selector to a component. They should always be used in mapStateToProps. React-Redux will reevaluate this when state changes and will tell React to update your components whenever the result is different. This is a very important point. If you just grab the state inside a component, whether using a selector or not, then React-Redux doesn't know you're using this data and won't we able to tell React to rerender when this data changes.
Now, a lot of people are confused on this matter. There are dumb components, which just display stuff, and container components, which do stuff, like make API calls or implement functionality of sorts. But when you take a dumb component and connect it to Redux, then this doesn't make for a smart or container component. It still only displays stuff. Even if you use mapDispatchToProps to feed it some event listeners, this still doesn't really make the component smart. It could become smart if it contains significant code in mapStateToProps or mapDispatchToProps I guess. But such is life. The line between these things is just blurry.
The Redux Way is to connect everything that needs data. You can certainly pass data down to children, just as in plain React, but you create a more performant app by connecting components. Still, it's up to you to decide. But it is still important that anywhere you grab data from the store, it should be put inside a mapStateToProps so React-Redux can keep an eye on the data. You can safely pass it from a parent to a child as long as the data came from mapStateToProps.
This means passing selectors to children is a no-no. Also, where's the child going to get the state to pass as a parameter to the selectors? It doesn't work well so it's not a good idea. Note that whenever you connect a component, you're not creating an entirely new component. Is just a simple wrapper. It should contain very little code in very few lines. This should not give you pause. Just go for it. connect those components.
I should also mention that you can connect your Weather component directly inside the weather.js file. Unless you're going to reuse it, there's not much need to keep the unconnected component around. For testing you can export the unconnected component with a named export. If later on you decide you need to reuse theWeather component, you can always easily separate the component and the connect call into separate files.