Jest store state and RTL rendered component's onClick event handler different states - react-redux

I'm using the following code to test a state-dependent react component using jest and rtl:
test("render author, date and image correctly after going next post", async () => {
const store = configureStore({
reducer: {
data: dataReducer
}
});
const Wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
render(<Post />, { wrapper: Wrapper });
const getSpy = jest.spyOn(axios, 'get').mockReturnValue(mockPostJSON);
await store.dispatch(FETCH_POSTS());
expect(getSpy).toBeCalledWith('https://www.reddit.com/r/EarthPorn/.json');
const beforeClick = await screen.findByTestId('authorAndDate');
expect(beforeClick.innerHTML.toString()).toBe(mockPostsList[0].author + ' - ' + mockPostsList[0].date);
fireEvent.click(screen.getByText('Next post'));
const afterClick = await screen.findByTestId('authorAndDate');
expect(afterClick.innerHTML.toString()).toBe(mockPostsList[1].author + ' - ' + mockPostsList[1].date);
})
The problem I'm having is that before the click everything in the store is set up correctly and the authorAndDate element displays the first item in the array of posts. But after the click is fired the store goes back to the initial state it had before loading the mock data. I checked within the component's event handler and right before it does anything the state has been reset. The code is as follows:
const handleNextClick = () => {
store.dispatch(GO_NEXT_POST());
store.dispatch(FETCH_COMMENTS());
}
I've been an hour over the code trying to find something that would reset the state and found nothing. I'm guessing it's some kind of interaction between jest and rtl but I can't figure out why the store in the test has one state and the store in the component's event handler has another :S

Well, figured it out. Can't use store.dispatch directly as it's accessing a stale state. Needed to use the useDispatch hook. Hope this serves anybody who faces the same problem in the future.

Related

Can't clear inputs when using Remix.js Form element

when submitting remix form with <Form>element i can't clear inputs after submit. In my particular case, that form is sitting on child route within <Outlet /> component (using nested routes here)
When form is submitted, all is working fine, redirect in handler goes to parent, parent is refreshing but child doesn't and inputs remains with values entered, that's a problem.
form is quite regular, inputs and button, all manages action function in parent
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData()
const data = Object.fromEntries(formData)
await doSomething(data)
return redirect('/route/add')
}
When instead of remix <Form/>element i use regular <form> element (its the only change) - total form refresh happen - and its also working, but extra request processing, and is impossible to use tasty remix hooks, like useTransition
how i cut the corner
const submit = useSubmit()
function submitHandler(evt) {
evt.preventDefault()
// total shame
evt.target.title.value = ''
evt.target.body.value = ''
submit(evt.target, {
action: '/route/add',
method: 'post',
})
}
so question is: how clear inputs after submitting ?
This is actually more of a React question instead of a Remix one.
Remix doesn't unmount your Outlet on navigation.
Remix is simply fetching data from your loaders and React is then rendering. As you know, initial state and default value do not get reset on re-render. The component must unmount. The simplest way is to update the key prop to trigger React to remount.

reactJS - Adding an event handler to an element which might, or might not exist at this time

I am not allowed to answer questions yet, but I feel that since this issue has taken me some days to resolve, I should post the solution the only way I can - as a question.
If you have a better solution then please include that in your reply.
Until an element is rendered it is not in the DOM so if you add an event listener to the element in your code you will get an error (element value null).
But you can add a listener to the root element, which is always there. WHen the event is triggered you can then retrieve the className and ID of the element involved in your event-handler.
Code:
var rootElement = document.getElementById('root');
console.log(rootElement);
rootElement.addEventListener('click', rootElementClicked);
console.log('event listener added to root element');
function rootElementClicked(event) {
event.preventDefault();
const { name, value } = event.target;
console.log("Root element clicked with [" + event.target.className, event.target.id);
}
/code
So, the event-listener is app-wide, so a click anywhere will call the event-handler function. Then in the code for that function, the element class and ID will tell you what was clicked.
Note the event.preventDefault(); line - it prevents a refresh of the web page, otherwise the target class & ID are returned as "undefined"
In React you shouldn't need to addEventListener manually since you can use onClick. However if you would like to manually attach click handler on an HTML element in React you can use React Ref. If you pass a ref object to React with <div ref={myRef} /> React will set its .current property to the corresponding DOM node whenever that node changes.
Example: https://codesandbox.io/s/react-hooks-useref-xfvlb
const App = () => {
const elementRef = useRef();
useEffect(() => {
elementRef.current.addEventListener('click', handleOnClick);
},[elementRef])
const handleOnClick = () => {
alert("click")
}
return (
<div className="App">
<div ref={elementRef}>
click on me
</div>
</div>
);
}

react-redux together with components status

in a react UI I have a table component. You can edit one row of the table by clicking a edit button or you can add a new record by clicking a "new-record-button". When clicking the edit button an redux-action is triggered which takes the row and sets a visible property of a modal dialog. When the "new-record-button" is clicked an action is triggered which creates a new empty data item and the same modal dialog is triggered.
In the modal dialog I have several text components with onChange method.
in this onChange-method the data-item is written.
When to user clicks a save-button the edited dataItem is saved to the database.
So my code looks like:
const mapStateToProps = (state) => ({
dataItem: state.datItemToEdit || {},
...
});
...
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
dataItem.carId = text;
break;
}
...
}
this.forceUpdate();
}
...
<TextField
...
onChange={event => this.handleTextChange(event)}
/>
I have several question regarding this approach. First I do not understand why in handleTextChange we can write to dataItem. It does work apparently.
dataItem.carId is set in the example code but I thought
const {dataItem} = this.props;
gives us a local read-only variable dataItem just to read from the props...
Next thing I think is a poor design. After reading in a book about react I think we should not write to props but only set a state.
In my example I get the the dataItem from the redux-state. The mapStateToProps maps it to the (read-only) props of the component, right?!. But I want to EDIT it. So I would have to copy it to the state of my component?
But where to do it?
Once in the state of my component I could simply call this.setState for the various text-fields and the component would render and I could abstain from forceUpdate(), right?!
Can someone explain how the redux status plays together with the component status and props for this example?
In redux or react, you shouldn't write to the props directly because you should keep your props as immutable. Redux forces us to use immutable state because state is a source of truth for the application. If the reference to state changes then only your app should render. If you'll mutate your state (objects) then the references don't get changed and your app doesn't know whether some state has been changed or not. React/Redux doesn't give you read-only objects automatically. You can mutate them anytime but as I told you, it can cause problems that Your app won't know when to re-render. If you want to have this read-only property inherently, you should probably use immutable.js
About your second question that you'll have to copy the props to the component's state and where you should do it. You should do it in the constructor of the component and you should use immutibility helper
import React from React;
import update from 'immutibility-helper';
class Modal extends React.Component {
constructor(props){
this.state = {
dataItem: dataItem,
};
}
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.state.dataItem, text); //fire a redux action to update state in redux
this.setState(update(this.state, {
dataItem: {
carId: {$set: text},
}
});
break;
}
...
}
}
}
You wouldn't have to do forceUpdate in such case because the reference to state will change and the component will re-render itself.
Also, you can use forceUpdate in your application but personally I don't find it a great idea because when React/Redux is giving you the flow of state, by using forceUpdate, you're breaking the flow.
The last question is how redux and react state plays together. That is also a matter of choice. If I have a app level state, e.g., in your case you've some app level data, you should put that in your redux state and if you have a component level things, such as opening a modal or opening a third pane. That's the convention I follow but that can really depend on how you want to exploit react and redux state.
Also, in above code, I put the redux state in component state too (because you asked where to put that) but Ideally you should fire a redux action and update in redux state. In this way, you will restrict yourself from state duplication in react and redux.
import React from React;
import {updateItem} from './actions';
class Modal extends React.Component {
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.props.dataItem, text); //fire a redux action to update state in redux
break;
}
...
}
}
}
const mapStateToProps = (state) => ({
dataItem: getDataItem(state), //get Data Item gets Data from redux state
});
export default connect(mapStateToProps, {updateItem: updateItem})(Modal);
in Actions:
updateItem = (dataItem, text) => dispatch => {
dispatch({type: 'UPDATE_ITEM', payLoad: {dataItem, text});
};
in Reducer:
export default (state = {}, action) => {
switch(action){
case 'UPDATE_ITEM': {
return {
...state,
dataItem: {
...action.dataItem,
carId: action.text,
}
};
}
}
}
In this way, your state will be pure and you don't have to worry about immutibility.
EDIT:
As constructor will be called only once, you should probably use componentWillReceiveProps so that whenever you render the component, you get the next updated props of the component. You can check whether the carId of dataItem is same or not and then update the state.
componentWillReceiveProps(nextProps){
if(nextProps.dataItem.carId !== this.props.dataItem.carId){
this.setState({dataItem: nextProps.dataItem});
}
}
You should only use redux when you want different, unrelated components in your app to know and share the specific state.
e.g. - When a user logs in to your app, you might want all components to know that user so you'll connect your different containers to the user reducer and then propagate the user to the components.
Sounds like in this case you have a classic use case for using the inner state.
You can use the parent of all TextFields to maintain all rows, edit them by index, etc.
Once you start using redux, it's really easy to make the mistake of transferring the entire state of the components to the reducers, I've been there and stopped doing it a while ago :)

How to reset wizard style redux form?

I am using the example http://redux-form.com/6.0.5/examples/wizard/ to accomplish my own form.
But how can I reset the first and second form when I use the wizard style form?
You can clear your form by calling dispatch(reset('myForm')); after you have submitted the data ideally at the parent component.
You can read more about it here
https://redux-form.com/6.7.0/docs/faq/howtoclear.md/
The tricky part is to make sure that dispatch is available at the component where you are submit (aka the parent component).
One way to do that is to wrap your parent component with connect(). After that, your component will receive a dispatch function as a prop. You can then use
this.props.dispatch.
This works for me
submitForm(values) {
const { dispatch } = this.props
axios.post(`YOUR_URL_HERE`, values)
.then(response => {
console.log(response.data)
dispatch(reset('MY_FORM'));
})
.catch(error => {
console.log("Unexpected error", error);
});
}

css manipulation happens before rendering so it wont be effective

I am using react and redux for my current project. I have a button and whenever user click on that it first call the server and load some data and then manipulate the css of some dom elements.
Here is my code:
var allClickableStories = document.getElementById("dummyClickStory" + this.props.story.id);
$(allClickableStories).click(function () {
if (!$("#" + this.id + " .expansion-handler").hasClass("show")) {
var storyId = this.id.replace("dummyClickStory", "");
thisRef.props.getStoryDetail(storyId, thisRef.props.channel);
$("#" + this.id + " .expansion-handler").addClass("show");
$("#" + this.id + " .cutline").addClass("show");
}
});
Also it is noteworthy that the above code in in componentDidMount to make sure that first render happens. However this does not guarantee that ajax call ( thisRef.props.getStoryDetail) happens before css manipulation and this is exactly where I am stuck at. what is happenning is the ajax call is sent and then css manipulation fires however ajax call may return after and render will happend and hide the manipulated dom element again.An easy way to fix it is to set asynch to false in jquery ajax call but not a good solution. So how can I can make sure that first ajax call finishes and render happens then css manipulation takes place?
Also just for more info here are my code in Action and reducer:
Action:
export function getStoryDetail(storyId,channel){
return dispatch => {
$.ajax({
url: "http://localhost:3003/json5.txt",
dataType: 'json',
cache: false,
success: function(data) {
var storyDetatil=[];
for (var key in data) {
storyDetatil.push(data[key]);
}
var storyDetailObj={"storyArray":storyDetatil,"storyId":storyId, "channel":channel};
dispatch({
type: "STORY_EXPANSION",
payload: storyDetailObj
});
}.bind(this)
});
};
}
Reducer:
case "STORY_EXPANSION":
var tempStateExpansion = state.slice();
if (action.payload.storyId > -1 && state[0].channel !=undefined) {
for(var i=0;i<state.length;i++){
if(state[i].channel.toLowerCase()===action.payload.channel.toLowerCase()){
for(var j=0;j<state[i].storiesSnippet.length;j++){
if(action.payload.storyId===state[i].storiesSnippet[j].id){
tempStateExpansion[i].storiesSnippet[j]=action.payload.storyArray[0];
break;
}
}
break;
}
}
}
else{
tempStateExpansion[0].storiesSnippet[0]=action.payload.storyArray[0];
}
state=tempStateExpansion;
break;
In short, you can not do this with React. You are using jQuery to manipulate the DOM which is basically the opposite approach to React. React manipulates the DOM for you.
What you should do instead is to have a component (you could look at inline styles perhaps: https://facebook.github.io/react/docs/dom-elements.html) that will rerender based on an updated state.
App renders with whatever defaults and fires off ajax request.
Ajax response updates the redux store, which through connect and a mapStateToProps updates the props of a component that should change when the ajax request is fulfilled.
Component rerenders based on the new state. The render path has the new styles (possibly inline)
I would recommend running through the TODO List example with redux and React here: http://redux.js.org/docs/basics/UsageWithReact.html. Your Redux usage looks alright, it's the React that is problematic.
Here is an example of using a React component which would rerender based on a data property of the redux state. It assumes that you would have an application wide CSS that contains definitions for foo and bar CSS classes.
import React from 'react';
import { connect } from 'react-redux';
class Example extends React.Component {
render() {
const { data } = this.props;
// If data is present (render a div using CSS from class foo)
// and put the data in the div asuming it's a string
if (data) {
return <div className='foo'>{ data }</div>;
}
// If data is not present (render a div using CSS from class bar)
// Display No Content
return <div className='bar'>No Content</div>;
}
}
// Data is an object, but not required as initially it will be undefined
Example.propTypes = {
data: React.PropTypes.object
};
// Map redux state to react props
const mapStateToProps = state => ({
data: state.data
});
// Connect to redux store helper using our mapping function
export default connect(mapStateToProps)(Example);

Resources