I'm trying to have 1 big global state where I can perform actions, but I don't want all those actions to live in the same file.
I want to break actions out to their own files and abstract with a changeState function (like a reducer), but I'm not sure how to do this.
I have an example here. If you click the button, it will show you how far through the app it's gotten: https://codesandbox.io/s/r49qyymjzn
It seems to never hit the {ctx => { console.log('...') }.
Any help would be much appreciated, thank you.
Think of the Context.Provider as a stateful component. The action changeName needs to update the state of the Context.Provider class.
Changes in context.js
handleNameChange = changeName;
actions: {
changeName: this.handleNameChange
}
changeName.js
export default (e, newName) => {
e.preventDefault();
this.setState({ name: newName });
};
Working sandbox example here
Related
I'm doing to do list and want do the functional when you click on button "done" the text will be crossed out.
I done array with deals which have fields 'text' and 'isDone'. isDone by default is false, when on click I get all array with deals and text deal in which you click. Than I map array with deals that I get and compare text from click deal and text in all deals in array.If they the same I change isDone from false to true.
But it update if i refresh the page and I need that it updata on click.
I use redux-persist and all states put into localStorage
button
<button onClick={()=>this.props.done(this.props.deals,value.text)}>Done</button>
Action
export function done(newDeals,dealText){
return(dispatch) =>{
newDeals.map(value=>{
if(value.text === dealText){
value.isDone = !value.isDone
}
})
dispatch(doneDeal(newDeals));
}
}
export function doneDeal(newDeals){
return{
type: DONE,
newDeals
}
}
Reducer
export default function toDoList(state = initialState, action){
switch(action.type){
case DONE:
return {
...state, deals: action.newDeals
}
default:
return state
}
}
I delete code that have no sense for this example, but need more info please ask I will tell
Thank you!
You have to use mapStateToProps to get the recently updated state from Redux state.
All what you need is to wrap your component export with the following:
const mapStateToProps = state => ({
deals: state.deals
});
export default connect(mapStateToProps)(ComponentName);
By this you are getting the needed state data from the initialState you have defined in your reducer and the reference to these data is the "deals" which can be used as normal prop: this.props.deals in case of class component OR as parameter through descructureing ({deals}) in case of functional component.
I don't know the full structure of the component because you haven't added it but this is the correct way to get the state from redux.
To make things more clear for you, you can read more through this link:
https://react-redux.js.org/using-react-redux/connect-mapstate
UPDATE: I figured out your problem after adding your reply.
The problem is with your code here:
<button onClick={()=>this.props.done(this.props.deals,value.text)}>Done</button>
You are getting the deals directly from the Redux global state and when dispatching your action you are passing them directly in the dispatch method. So your component is not able to listen to any change happening to your component. You need to save the deals in your local state:
state = {
deals: this.props.deals
}
and change the onClick to the following:
<button onClick={()=>this.props.done(this.state.deals,value.text)}>Done</button>
I have a component which is a form which I use to create records in my database. I also want to be able to pass into this component values with which to populate the form, allowing me to then update my existing database records. Straightforward add/edit functionality from the same component.
The following code should explain how I am doing this. The media prop is an object containing the data. I have this data already in the parent element so setting the values here is fine and they pass thru without problem. However once the page is loaded the 3rd init argument of useReducer never re-triggers, and therefore my state cannot be overridden with the values passed down in the media prop. Is there a correct way to make the init function trigger when the props are updated, or is my issue architectural?
const MediaUploadForm = ({ media }) => {
const init = (initialState) => {
if (media) {
// ... here I extract the values I need and override the initialState where required
} else {
return initialState
}
}
const [formState, dispatch] = useReducer(
MediaFormReducer,
initialState,
init
)
So using the new React hooks features and keeping the component functional allows me to use useEffects() This is similar to using a componentDidUpdate type event. So the following code allows me to check for the status of a prop (media) and then dispatch an action that sets my redux state.
useEffect(() => {
if (media && id !== media.id) {
dispatch(loadMedia(media))
}
})
Thanks to #sliptype for pointing me in the right direction
Copying props into state is considered an anti pattern in React. Props changes do not trigger reinitialising state, as you have seen.
This is described in https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
From the recap it looks like the current suggested solution matches
Alternative 1: To reset only certain state fields, watch for changes in a special property (e.g. props.userID).
This is an alternative, rather than the recommendation.
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recap
Hope this link gives you more information around the topic, and the recommendations there help in future work.
Question: Is it possible to use createStore() from redux in a component?
If so, how to do it properly?
I'm trying to understand react and redux by tranforming this stopwatch example to a component-based approach.
The original approach is as follows:
let container = Redux.createStore((model = { running: false, time: 0 }, action) => {
const updates = {
'START': (model) => Object.assign(model, {running: true}),
'STOP': (model) => Object.assign(model, {running: false}),
'TICK': (model) => Object.assign(model, {time: model.time + (model.running ? 1 : 0)})
};
return (updates[action.type] || (() => model))(model);
});
And
const render = () => {
ReactDOM.render(view(container.getState()),
document.getElementById('root')
);
};
What I get so far is this, which does not show the counter div.
Note that I'm trying to understand the reasonings behind react and redux, thus avoiding the use of react-redux intentionally.
It's technically possible, but that's not how you should do it.
In React, you want the output of render method to be only dependent on component's props and state. So what you want to do, is to define a component that expects something outside of it to provide the props, and then make Redux be that provider.
You can read more about it here.
There was another issue with your example: Redux wants the reducer (the method you pass to createStore) to not mutate the previous state. Object.assign(state, ...) does mutate object, so it needs to be changed to Object.assign({}, state, ...). More on that here.
I have a working snippet for you, but I strongly recomment following the official guides :)
I don't really know why I can't get this to work. All the evidence talks against it...This is the situation:
I have a grid of data and a search panel. When the search panel is changed the searchparams are updated and used for updating the data grid.
The thing which triggers the chain is when the user changes the search panel. In my component i handle search panel changes with this:
getPhotos(key, value) {
const change = [{ key: key, value: value},{ key: 'page', value: 1}]
this.props.dispatch(updateSearchParams(change))
console.log('payload.searchParams', this.props.searchParams);
this.props.dispatch(
getPhotos(
{ context:this.props.params.context,
searchParams: this.props.searchParams }
)
);
}
Thus two dispatch calls to action creators form the component. The problem is that the searchparams are not updated in time for the getPhotos call, so the grid is not updated accordingly.
I thought that dispatch calls were synchronous - thus one after the other. I guess that it is the round trip from the component, to the action creator, to the store and reducer which is "screwing" it up.
The first call does not involve any asynchronous calls.
What is the "right" way of doing this? Please be specific about what goes in the component, the action creator and the reducer.
Thanks
dispatch is synchronous (unless you are using some middleware like redux-thunk). But after this.props.dispatch(updateSearchParams(change))
, your component needs to be updated (a re-render) or the this.props.searchParams is still the old one.
You can write this.props.dispatch(getPhotos(...)) in componentWillReceiveProps(nextProps), so you can access the new props (nextProps)
If you are using redux-thunk and two actions updateSearchParams and getPhotos are always bind together, you can create another aggregated action creator for them.
const updateSearchParams = change => dispatch => {
// return a promise here
// or use callback style etc. whatever you prefered
}
const updateSearchParamsAndGetPhotos = (change, context) => dispatch => {
dispatch(updateSearchParams(change))
.then(res => {
dispatch(getPhotos({
context,
searchParams: res.data.searchParams
}))
})
}
So now after dispatching a single action, your component should receive the new photos.
I had it wrong from the beginning.
The searchparams should not go into the store. I can handle the in the component alone - in the state of the component.
This the simplifies and eliminates the problem I described above.
Of cause there could be a situation where the searchparams needed to be available for other components. In that case I would go for #CodinCat answer above with the thunk. It works, i managed to implement it before my realisation.
Thanks
There are a few other hello world app questions with regard to react, but mine is specific to the reducer. Im not exactly sure what I should put in the reducer for my specific action.
Note*: I thought maybe i need to add a message: "" key value pair to my initial state and then declare a
var newState = Object.assign({}, state, {
message:"hello world"
});
into my if statement in the reducer, then dispatch it in the component, but that seems unneccesary since it should always print hello world, so hard coding seems more efficient. Hopefully there isn't too much clutter in this question as well.
Here is my component:
var HelloWorld = React.createClass({
helloWorld: function() {
this.props.dispatch(actions.printHello());
},
render: function() {
return (
<div className="HelloWorld">
<h1>Hello World!</h1>
</div>
);
}
});
var Container = connect()(HelloWorld);
module.exports = Container;
my action:
var $ = require("jquery")
var PRINT_HELLO = 'PRINT_HELLO';
var printHello = function() {
return {
type: GUESS_NUM
};
};
exports.PRINT_HELLO = PRINT_HELLO;
exports.printHello = printHello;
and reducer:
var actions = require('./actions');
var initialRepositoryState = {
type: null
};
var capstoneApp = function (state,action) {
state = state || initialRepositoryState;
if (action.type === actions.PRINT_HELLO) {
var newState = Object.assign({}, state, {
message:"hello world"
});
return newState;
}
};
I don't think you will need my index.js but I will provide if necessary.
Thanks in advance for input!
The reducer holds your state. So the way of thinking about it is "what are the parts of my program that can change?" And then from there, the next question is "What is the minimal amount of state needed to hold on to my program?"
From looking at your example, I get the impression that you are trying to build an application that sometimes displays "hello world" in the UI. Let me make a few more interactions to help describe all the pieces.
Initially, I'll help you create a program that has an empty label and a button. When you click on the button, it will display "hello world".
Okay, so to answer the first question: What can change? The app can either display "hello world" or nothing. We could store that in a couple of different ways. If the string is hard-coded, like you've alluded to above, then really you have a bool of show the message, or not.
And to the second question: one truthy value is pretty much the definition of a minimal state. So let's make a reducer:
var initialState = {
showMessage: false,
};
var reducer = function(state, action) {
var newState = Object.assign({}, state);
if (action.type === 'BUTTON_PRESS') {
newState.showMessage = !newState.showMessage;
}
return newState;
}
Okay, so now we've created a reducer that, when it gets the BUTTON_PRESS action, it flips the bit of its state.
Now, let's talk about how to hook that reducer up to redux. Right now, the reducer is a plain javascript function. We just need to pass that store into redux with the initial state. [createStore][1]
P.S. I normally write ES2015 code so there may be small typos in the commonJS import syntax
var redux = require('redux');
var store = redux.createStore(reducer, initialState);
The next part is to look at redux-react.
Redux-react is glue that works both ways in react. It connects data from the redux store to react props, and it connects react callbacks (such as a click) to redux actions.
So conceptually, it looks like this. You have a react component that has a button. When you click the button, we want to generate a 'BUTTON_PRESS' action. After this, your react component no longer cares what happens to BUTTON_PRESS. Its job is done. BUTTON_PRESS could do one of infinite things. Now, assume that redux-react does its job and gets the action passed to the reducer. The reducer computes its new logic and returns a new state. This new state has the value of showMessage. Then, redux-react does the other half of connecting and makes showMessage a prop for the component. Just to be clear, there is no explicit reason why the same react component has to respond to the state changed by your action. They could be different components.
To put it into bullet points, here is how the codeflow should work:
We create an initial store with showMessage = false
When creating the React component, we use connect to bind the showMessage to a prop, and to handle onClick of the button in the component to generate a 'BUTTON_PRESS' action.
Since showMessage is false, there is originally only a button present.
The user presses the button. React calls into the onClick handler
We use redux-react to dispatch a BUTTON_PRESS event
When redux gets an action, it calls the reducer with the current state and the action. The reducer is responsible for generating a new state in response to this action
The reducer sets showMessage to true
redux-react listens to store changes and when it changes it modifies the prop for the react component
The prop changes so react calls render()
Inside your render method you see that this.props.showMessage is true, so you display the message.
And here is how such a react component could be implemented. There are enough differences between React components in ES5 vs ES2015 that I'm just going to give you the ES2015 version and apologize again.
class HelloWorld extends Component {
render() {
const message = this.props.showMessage ? "Hello world!" : "";
return (
<div id="label">{message}</div>
<div id="button" onClick={this.props.handleOnClick}>Toggle the message </div>
);
}
}
HelloWorld.propTypes = {
showMessage: PropTypes.bool.isRequired,
handleOnClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
showMessage: state.showMessage,
});
const mapDispatchToProps = dispatch => ({
handleOnClick: dispatch({ type: 'BUTTON_PRESS', payload: '' })
});
export default connect(mapStateToProps, mapDispatchToProps)(HelloWorld);
You can keep reading the docs but I hope that helps explain what all the parts are. Let me know if anything isn't clear.
[1]: http://redux.js.org/docs/api/createStore.html