I am realatively new in React JS. Few weeks ago I created To Do List app in JS, jQuery and now I am going to rebuilt it using React, just for change my point of view and practice React.
I have few components (siblings) in different files and one parent component - App, components:
App:
- Navigation
- Task List
- Add Task
- Footer
How can my navigation component communicate with task list component?
To be more specific I want to have something like global variable selectedDay and use it in all components.
When user choose in Navigation component single day, for example Sunday , I want to save "sunday" in this variable and later use it in Task List (this is of course sample example of data). My question is how to store data in first component and use it in another one?
Should I use state for this kind of purposes? I was thinking about set initial state in parent (App) component -> selectedDay : "monday" /default/ and later update it by Navigation component and use in Task List component. Could you help me, please? I will be gratefull!
There are two solutions for this.
1- Use a library that handles a global state, like Redux (as FurkanO said). That way, your "big components" (aka containers) are connected to the global state of your application, and update it with actions.
Actions are some kind of events with a type, and sometimes a payload, that will be intercepted by a reducer and trigger a state update.
2- Use the state of the lowest common parent of the components you want to see interracting.
Basic Example for 2- : Parent Component contains Navigation & TaskList.
class Parent extends Component {
state = {
selectedDay: defaultDay,
}
setDay = (selectedDay) => {
this.setState({ selectedDay });
}
render () {
const { selectedDay } = this.state;
return (
<div>
<Navigation setDay={this.setDay} />
<TaskList selectedDay={selectedDay} />
</div>
);
}
}
Then you just use this setDay function in your Navigation component to set the state in the Parent Component. That way, your TaskList will receive the new value via its props.
This method has its limits (it really doesn't scale well in my opinion).
Hope that helped. Please tell me if this isn't clear for you.
What you need is redux. It provides you a global state tree which is an object, a way to manipulate it and most importantly whenever your state tree changes rerenders your components. So that your components are always up-to-date with updated state tree.
Related
I have a React application using Material UI with a component (which we can call DatePicker) shown below, sneakily changed for demo purposes.
Material UI animates clicks and other interactions with its components. When clicking a radio button that has already been selected, or a "time button" which doesn't change state, this animation is visible above. However, when such a click changes the state, the animation get interrupted.
I can see why this happens from a technical perspective; the DatePicker component calls setMinutes, which is a property passed in from its parent (where the state lives). This is a React.useState variable, which then updates its corresponding minutes variable. Minutes is then passed into DatePicker, which re-renders due to a prop change.
If state lived within DatePicker then this problem shouldn't rear its head; however, DatePicker is one part of a much larger form which dictates the contents of a table in the parent. To generate rows for this table, the parent must have this information.
Below is a sample reconstruction of the parent:
const Parent = () => {
const [minutes, setMinutes] = React.useState(15);
const [radioOption, setRadioOption] = React.useState('Thank You');
// Many other state variables here to hold other filter information
return (<div>
<DatePicker minutes={minutes} setMinutes={setMinutes} radioOption={radioOption} setRadioOption={setRadioOption}/>
</div>);
};
And here a sample reconstruction of DatePicker:
const DatePicker: React.FC<DatePickerProps> = props => {
const {minutes, setMinutes, radioOption, setRadioOption} = props;
return (<div>
<Radios value={radioOption} onChange={val => setRadioOption(val)}/>
<Minutes value={minutes} onChange{val => setMinutes(val)}/>
</div>);
};
I'm not sure what the best practice is in this situation, but I get the distinct feeling that this is not it. Does anyone have any advice? Thanks in advance!
Thank you for your comment, Ryan Cogswell. I did create a code sandbox, and found that the problem was not about React state management as much as what I was doing beyond what I provided in my question.
I was using the withStyles HOC to wrap my component, in a way similar to const StyledDatePicker = withStyles(styles)(DatePicker). I then used that styled element and put properties (minutes, etc) on that.
It turns out that using the unstyled DatePicker resolves this issue. I troubleshooted this further, and found that I had created the "Styled" component within the "render" method of the parent, meaning every time a prop change was pushed up the chain, the parent would re-render and the entire "Styled" component type would be created again (or so I believe). This would break reference integrity, which explains the "drop and recreate" behaviour.
This teaches the valuable lesson of keeping components small and using code sandboxes for troubleshooting. Thanks again!
For anyone interested, here is the Code Sandbox used for testing.
i tried to update the sound array which i imported from other component every time it is changed. But however, it only fire componentDidMount() only once and it won't run again. Down below is my code on the problem:
//sound array from another component
import { soundArray } from "./CreateRecord";
export default class RecordingList extends React.Component {
constructor() {
super();
this.currentSoundArray = [];
}
componentDidMount() {
console.log(this.currentSoundArray);
this.getCurrentArray();
}
getCurrentArray() {
this.currentSoundArray = soundArray;
}
render(){
...
}
currently when i view the component, the componentDidMound will run once and console the sound array. At first, the sound array is empty:
[]
However, after i put value in the sound array and comeback to view the component, it wont print the console and it won't update the value of this.currentSoundArray
My expected result should be the currentSoundArray will be changed and print to the console every time the soundArray has been changed in another component. for example:
[]
[1,2]
[1,2,4]
componentDidMount() is invoked immediately after a component is mounted (inserted into the tree). Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
It runs only once.
What you are trying to do is access currentSoundArray value when it is updated from another component, now that you will not be able to do traditionally.
Also componentDidMount fires only once when the component is first initialized and rendered.
Solution 1
A better way of doing this is using something like React Redux to manage your application state, this way you would be able to access the states from any component throughout your application.
I have just finished setting up a boiler plate template for this exact thing, if you would like to check it out its on Github :)
Solution 2
If you are not interested in Redux then i would suggest you use react context for this, it will solve your issue as well. you can check out many examples online for example this uses context to share a snackbar across components.
Hope this Helps!
I made a Todo list with React js. This web has List and Detail pages.
There is a list and 1 list has 10 items. When user scroll bottom, next page data will be loaded.
user click 40th item -> watch detail page (react-router) -> click back button
The main page scroll top of the page and get 1st page data again.
How to restore scroll position and datas without Ajax call?
When I used Vue js, i’ve used 'keep-alive' element.
Help me. Thank you :)
If you are working with react-router
Component can not be cached while going forward or back which lead to losing data and interaction while using Route
Component would be unmounted when Route was unmatched
After reading source code of Route we found that using children prop as a function could help to control rendering behavior.
Hiding instead of Removing would fix this issue.
I am already fixed it with my tools react-router-cache-route
Usage
Replace <Route> with <CacheRoute>
Replace <Switch> with <CacheSwitch>
If you want real <KeepAlive /> for React
I have my implementation react-activation
Online Demo
Usage
import KeepAlive, { AliveScope } from 'react-activation'
function App() {
const [show, setShow] = useState(true)
return (
<AliveScope>
<button onClick={() => setShow(show => !show)}>Toggle</button>
{show && (
<KeepAlive>
<Test />
</KeepAlive>
)}
</AliveScope>
)
}
The implementation principle is easy to say.
Because React will unload components that are in the intrinsic component hierarchy, we need to extract the components in <KeepAlive>, that is, their children props, and render them into a component that will not be unloaded.
Until now the awnser is no unfortunately. But there's a issue about it in React repository: https://github.com/facebook/react/issues/12039
keep-alive is really nice. Generally, if you want to preserve state, you look at using a Flux (Redux lib) design pattern to store your data in a global store. You can even add this to a single component use case and not use it anywhere else if you wish.
If you need to keep the component around you can look at hoisting the component up and adding a "display: none" style to the component there. This will preserve the Node and thus the component state along with it.
Worth noting also is the "key" field helps the React engine figure out what tree should be unmounted and what should be kept. If you have the same component and want to preserve its state across multiple usages, maintain the key value. Conversely, if you want to ensure an unmount, just change the key value.
While searching for the same, I found this library, which is said to be doing the same. Have not used though - https://www.npmjs.com/package/react-keep-alive
I am struggling a bit with the concept of global state and reusable components in redux.
Let's say I have a component that's a file-selector that I want to use in multiple places inside my applications state. Creating action/reducers leads to a lot of bloat as I have to handle states with dynamic suffixes and other weird things that just don't really strike me as a smart way to go about things.
What's the general consensus on these things? I can only see two solutions:
Make the file-selector component have local state (this.setState/this.getState)
Make the file-selector be part of the global state but in it's own unique reducer that I can read from once the operation of the component is done?
Any ideas / best practices? Thanks.
Update: To clarify the file selector I am describing is not a simple component that works purely on the client side but has to fetch data from the server, provide pagination as well as filtering etc.. That's why I'd also like to reuse most of the client/server interaction. The views that display this component are of course dumb and only display values from the state - but how do I reuse the actions/reducers in multiple places around the application?
Have your reducer handle multiple instances of your component state. Simply define some "unique" ID for each instance of your FileBrowser component when it appears in the app, and wrap your current state in an object with this uniqueIds as keys, and your old complex state as value.
This is a technique I've used multiple times. If all your FileBrowser are known at compile time, you can even setup the initial state before running your app. If you need to support "dynamic" instances, simply create an Action that initializes the state for a given id.
You didn't provide any code, but here's a contrived example for a reusable Todo reducer:
function todos(state={}, action){
switch(action.type){
case 'ADD_TODO':
const id = action.todoListId
return {
...state,
[id]: {
...state[id],
todos: [ ...state[id].todos, action.payload ]
}
}
// ...
}
}
Usually, the rule of thumb is that you use a redux store to manage data in your application aka storing items fetched from the server and local react state for ui behaviors, like file uploads in your case. I'd make a pure react component to manage file uploads and then use redux-form to manage specific form.
Here is the example of the component I use in my project
import React, {Component, PropTypes} from 'react';
import Button from 'components/Button';
class FileButton extends Component {
static propTypes = {
accept: PropTypes.string,
children: PropTypes.any,
onChange: PropTypes.func.isRequired
};
render() {
const {accept, children, onChange} = this.props;
return <Button {...this.props} onClick={() => this.file.click()}>
<input
ref={el => this.file = $(el)}
type="file"
accept={accept}
style={{display: 'none'}}
onChange={onChange}
/>
{children}
</Button>;
}
}
export default FileButton;
We came to the conclusion that reusable components must be of two kinds:
dumb components, i.e. components that only receive props and trigger "actions" via props callbacks only. These components have minimal internal state or at all. These are the most frequent of reusable components, and your file selector will probably fall in that case. A styled Text Input or custom List would be good examples too.
connected components that provide their own actions and reducer. These components have their own life within the application and are rather independent from the rest. A typical example would be a "top error message box" that displays on top of everything else when the application fails critically. In such a case the application triggers an "error action" with the appropriate message as payload and on the following re-render, the message box displays on top of the rest.
I'd like my React based SPA to render on server side (who's not these days). Therefore I want to combine React with react-router, redux and some build layer like isomorphic starterkit.
There is hapi universal redux which joins all together, but I am struggling with how to organize my flow. My data is coming from multiple endpoints of a REST API. Different components have different data needs and should load data just in time on the client. On the server instead, all data for a specific route (set of components) has to be fetched, and the necessary components rendered to strings.
In my first approach I used redux's middleware to create async actions, which load the data, return a promise, and trigger a SOME_DATA_ARRIVED action when the promise resolves. Reducers then update my store, components re-render, all good. In principle, this works. But then I realized, that the flow becomes awkward, in the moment routing comes into play.
Some component that lists a number of data records has multiple links to filter the records. Every filtered data set should be available via it's own URL like /filter-by/:filter. So I use different <Link to={...}> components to change the URL on click and trigger the router. The router should update the store then according to the state represented by the current URL, which in turn causes a re-render of the relevant component.
That is not easy to achive. I tried componentWillUpdate first to trigger an action, which asynchronously loaded my data, populated the store and caused another re-render cycle for my component. But this does not work on the server, since only 3 lifecycle methods are supported.
So I am looking for the right way to organize this. User interactions with the app that change the apps state from the users perspective should update the URL. IMO this should make the router somehow load the necessary data, update the store, and start the reconciliation process.
So interaction -> URL change -> data fetching -> store update -> re-render.
This approach should work on the server also, since from the requested URL one should be able to determine the data to be loaded, generate initial state and pass that state into the store generation of redux. But I do not find a way to properly do that. So for me the following questions arise:
Is my approach wrong because there is something I do not understand / know yet?
Is it right to keep data loaded from REST API's in redux's store?
Is'nt it a bit awkward to have components which keep state in the redux store and others managing their state by themselfs?
Is the idea to have interaction -> URL change -> data fetching -> store update -> re-render simply wrong?
I am open for every kind of suggestion.
I did set up exactly the same thing today. What we already had, was a react-router and redux. We modularized some modules to inject things into them – and viola – it works. I used https://github.com/erikras/react-redux-universal-hot-example as a reference.
The parts:
1. router.js
We return a function (location, history, store) to set up the router using promises. routes is the route definition for the react-router containing all your components.
module.exports = function (location, history, store) {
return new Bluebird((resolve, reject) => {
Router.run(routes, location, (Handler, state) => {
const HandlerConnected = connect(_.identity)(Handler);
const component = (
<Provider store={store}>
{() => <HandlerConnected />}
</Provider>
);
resolve(component);
}).catch(console.error.bind(console));
});
};
2. store.js
You just pass the initial state to createStore(reducer, initialState). You just do this on the server and on the client. For the client you should make the state available via a script tag (ie. window.__initialstate__).
See http://rackt.github.io/redux/docs/recipes/ServerRendering.html for more information.
3. rendering on the server
Get your data, set up the initial state with that data (...data). createRouter = router.js from above. res.render is express rendering a jade template with the following
script.
window.csvistate.__initialstate__=!{initialState ? JSON.stringify(initialState) : 'null'};
...
#react-start
!= html
var initialState = { ...data };
var store = createStore(reducer, initialState);
createRouter(req.url, null, store).then(function (component) {
var html = React.renderToString(component);
res.render('community/neighbourhood', { html: html, initialState: initialState });
});
4. adapting the client
Your client can then do basically the same thing. location could be HistoryLocation from React-Router
const initialState = window.csvistate.__initialstate__;
const store = require('./store')(initialState);
router(location, null, store).then(component => {
React.render(component, document.getElementsByClassName('jsx-community-bulletinboard')[0]);
});
To answer your questions:
Your approach seems right. We do the same. One could even include the url as part of the state.
All state inside of the redux store is a good thing. This way you have one single source of truth.
We are still working out what should go where right now. Currently we request the data on componentDidMount on the server it should already be there.