I am working on a build/design system. and everything works fine. The only issue is when I publish my package and attempt to use the callback function. It doesn't properly return the data that is necessary for me to go to the next screen.
I attempted to get a reproducible example for you in CodeSandbox, however, there were some minor implications/errors that wouldn't allow me to get to the specific error I am talking to you about now. That seems to have its own issues.
So, how do you reproduce this error? Well, our package as of right now is public. As said above you can't import into CodeSandbox as it gives other errors on React versions (as said, I will deal with that later..). The package name is #sandboxcommerceeng/react the scss package you might need is #sandboxcommerceeng/scss. Go ahead and import into a CSS file. #import '#sandboxcommerceeng/scss/lib/global.css'. Then in the #sandboxcommerceeng/react package, import ECommercePlatformModal. The code below will give you a reproducible error. Platforms type, is also exported by #sandboxcommerceeng/react
const [showEcommerceModal, setShowEcommerceModal] = React.useState<boolean>(false);
const [url, setUrl] = React.useState<string>('');
const [selectedPlatform, setSelectedPlatform] = React.useState<keyof Platforms>();
<ECommercePlatformModal
selectedPlatform={selectedPlatform}
onSelectPlatform={(platform: keyof Platforms | undefined) =>
setSelectedPlatform(platform)
}
showModal={showEcommerceModal}
onCancel={() => setShowEcommerceModal(false)}
okButtonProps={{ onClick: () => console.log(url) }} // ✴
urlValidated={true}
onUrlChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setUrl(e.target.value);
}}
url={url}
/>
✴: I am unable to get the URL from the state using a callback. I've tried just referencing a callback function separately and then logging the URL from there. I've also tried pasting in the URL from the callback. Nothing is working.
End Goal
The end goal here is to have it so that I am able to apply my package into my React application and have the package functionality, i.e. the javascript code implemented in the package itself, work as expected. Expectation for this particular component, is to be able to console.log the url and have it show in the package from the callback function okButtonProp{{onClick: () => console.log(url)}}
Please let me know if there is anything else you need from me.
Your answer lies here. (it could be just a comment but don't have enough reputation yet :) ).
After debugging, the issue did lay within the build system itself. Using React.memo on a button component was the culprit. The memoization wasn't working as intended due to the fact that none of the props were actually changing on the component, therefore it wasn't re-rendering the button component with the new function and variables being passed down to it.
I went ahead and removed the memoization from the memoized component, as I couldn't see a way to fit memoization in this instance.
Related
In times past, my colleagues and I would typically write React Testing Library (RTL) tests for the main parent components, which often have many nested child components. That testing made sense and worked well. Btw the child components in question are very much dedicated to that parent component and not of the reusable variety.
But now we're trying to write RTL tests for every single component. Today I was trying to build tests for an Alerts component, which is the parent of an Alert component and about 4 levels down from the top-level component. Here's some sample code in my test file:
function renderDom(component, store) {
return {
...render(<Provider store={store}>{component}</Provider>),
store,
};
}
let store = configureStore(_initialState);
const spy = jest.spyOn(store, 'dispatch');
const { queryByTestId, queryByText, debug } = renderDom(
<Alerts question={store.getState().pageBuilder.userForm.steps[0].tasks[0].questions[1]} />,
store
);
I then started writing the typical RTL code to get the Alerts component to do its thing. One of these was to click on a button which would trigger an ADD_ALERT action. I stepped through all of the code and the Redux reducer was apparently working correctly with a new alert, as I intended, yet back in the Alerts component, question.alerts remained null whereas in the production code it was definitely being updated properly with a new alert.
I spoke with a colleague and he said that for this type of test, I would need to artificially rerender the component like this:
rerender(<Provider store={store}><Alerts question={store.getState().pageBuilder.userForm.steps[0].tasks[0].questions[1]} /></Provider>);
I tried this and it appears to be a solution. I don't fully understand why I have to do this and thought I'd reach out to the community to see if there was a way I could avoid using rerender.
It's hard to be certain without seeing more of your code, but my typical approach with RTL is to take the fireEvent call that simulates clicking the button and wrap it in an act call. This should cause React to finish processing any events from your event, update states, rerender, etc.
Alternatively, if you know that a particular DOM change should occur as a result of firing the event, you can use waitFor. An example from the React Testing Library intro:
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('alert'))
I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.
I often face this situation with react callbacks:
const MyComponent = ({ onLoad }) => {
useEffect => {
// some stuff
onLoad(/* args */);
}, [onLoad]);
return (<div />);
}
The problem is. I consider my component should only load one time. And with useEffect, i have to set onLoad in the dependencies, this cause any change to the onLoad prop to trigger the effect.
I generally solve this issue with a ref
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRef(_onLoad);
onLoadRef.current = _onLoad;
useEffect => {
// some stuff
onLoadRef.current(/* args */);
}, []); // No dependencies anymore
return (<div />);
}
It works well and solve a lot of similar problems, but i find it a bit ugly, and not really beginner-friendly. I wonder if there is better solutions, or if what i do is an anti-patern ?
As from the comments above: This is a good resource for how to use useEffect
https://reacttraining.com/blog/useEffect-is-not-the-new-componentDidMount/
This article specifically highlights the main reasons on why you need to think of useEffect differently from the Class Component lifecycle methods.
We often times do some setup when the component first mounts like a
network call or a subscription. We have taught ourselves to think in
terms of "moments in time" with things like componentDidMount(),
componentDidUpdate(), and componentWillUnmount(). It's natural to take
that prior knowledge of React and to seek 1:1 equivalents in hooks. I
did it myself and I think everyone does at first. Often times I'll
hear in my workshops...
"What is the hooks equivalent to [some lifecycle method]?"
The quick answer is that hooks are a paradigm shift from thinking in
terms of "lifecycles and time" to thinking in terms of "state and
synchronization with DOM". Trying to take the old paradigm and apply
it to hooks just doesn't work out very well and can hold you back.
It also gives a good run through of the useEffect and an example of converting from a Class Component to hooks.
Another good source is https://overreacted.io/a-complete-guide-to-useeffect/ from Dan Abramov. I definitely recommend this even though it's very long to read. It really helped me when I first got started using hooks to think about them the right way.
Here's a small excerpt from the beginning of the article.
But sometimes when you useEffect, the pieces don’t quite fit together.
You have a nagging feeling that you’re missing something. It seems
similar to class lifecycles… but is it really? You find yourself
asking questions like:
🤔 How do I replicate componentDidMount with useEffect?
🤔 How do I correctly fetch data inside useEffect? What is []?
🤔 Do I need to specify functions as effect dependencies or not?
🤔 Why do I sometimes get an infinite refetching loop?
🤔 Why do I sometimes get an old state or prop value inside my effect?
When I just started using Hooks, I was confused by all of those
questions too. Even when writing the initial docs, I didn’t have a
firm grasp on some of the subtleties. I’ve since had a few “aha”
moments that I want to share with you. This deep dive will make the
answers to these questions look obvious to you.
To see the answers, we need to take a step back. The goal of this
article isn’t to give you a list of bullet point recipes. It’s to help
you truly “grok” useEffect. There won’t be much to learn. In fact,
we’ll spend most of our time unlearning.
It’s only after I stopped looking at the useEffect Hook through the
prism of the familiar class lifecycle methods that everything came
together for me.
In terms of the original question above, using refs is a good way to be able to not have your effect have specific functions and values as dependencies.
In particular they are good if you "you want to read the latest rather than captured value inside some callback defined in an effect"
For this example from the poster:
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRef(_onLoad);
onLoadRef.current = _onLoad;
useEffect => {
// some stuff
onLoadRef.current(/* args */);
}, []); // No dependencies anymore
return (<div />);
}
This is a completely valid way of doing things, though depending on the args that onLoad takes, and how it works, it might be a good idea to add extra items to the dependency array to make it always in sync.
You could abstract away the wonkiness of the useRef here, but unfortunately the rules of hooks eslint plugin wouldn't recognize it as a ref. It would work, you'd just need to add the onLoadRef to the dependency array, though it would never cause the effect to re-run. It's similar to things like dispatch from react-redux where you know it is stable, but the eslint plugin can't know that.
function useRefUpdater(value) {
const ref = useRef(value);
// I forget where I saw that you should change the ref in a useEffect
useEffect(() => {
ref.current = value;
}, [value]);
return ref;
}
const MyComponent = ({ onLoad: _onLoad }) => {
const onLoadRef = useRefUpdater(_onLoad)
useEffect(() => {
// some stuff
onLoadRef.current(/* args */);
}, []);
// React Hook useEffect has a missing dependency: 'onLoadRef'. Either include it or remove the dependency array.
return <div />;
};
I currently import my component dynamically as they are needed, however if a lot of changes are required on a component, I would want to make a new version of it, however it would still be the same component in a way.
I have the following within my app.js:
Vue.component( 'favourites-panel', () => import('./components/Favourites/Panel.vue' );
Can I change the above to something like this and get the version from the prop? Obviously this is theoretical code!
Vue.component( 'favourites-panel', (e) => import('./components/Favourites/Panel' + e.version + '.vue' );
This is how i'm calling my component:
<favourites-panel version="1"></favourites-panel>
No, that's not possible "versioning" a component using the props object.
First of all, you need to understand what you are doing: Vue.component is a function to load globally all the components you want. You can pass an absolute path or if required, a promise.
In this case, you want to load your component asynchronously and the statement import, return a Promise. If you inspect the e property you'll see that is the resolve callback.
Writing:
Vue.component( 'favourites-panel', () => import('./components/Favourites/Panel.vue' );
or:
Vue.component('favourites-panel', function (resolve) {
require(['./components/Favourites/Panel.vue'], resolve)
})
It's the same thing, both returns a Promise object.
For solving your problem you can add an environment variable and then load the
component according to the value of that particular environment variable.
No, this is entirely the wrong approach to both source control and dependency management. You should instead be creating NPM modules of your component(s) and then if for some reason you need to use an old one again you can npm install the old version.
I want to test to see whether an image has properly loaded in a React app. I have decided to check the src attribute of the img element nested within the React component. I want to use the Jest testing framework and, if needed, the Enzyme testing utility.
By digging through the Object.keys of a shallow React test component, I was able to come up with the following solution. The line I'm uncertain about is indicated with the asterisks.
import React from 'react';
import {shallow} from 'enzyme';
import App from './App';
it('should have the logo image', () => {
const app = shallow(<App />);
const img = app.find('img');
const src = img.node.props.src; // ******
expect(src).toBe('logo.svg');
});
This solution does work but it seems a little convoluted (it requires a property of a property of a property of a wrapper) and seems somewhat obscure (I can't easily find clear instructions for this online). So, is this the correct/simplest way to do this?
If so, where is the documentation?
If not, how else should/could I be doing this? e.g. Is there some ready-made getAttribute or retrieveProp method, etc. in Enzyme? Is there a better way of doing this using something else instead of Enzyme, e.g. react-addons-test-utils?
This question about React element attributes doesn't seem to quite answer it for me even though the poster there also indicates having a hard time finding documentation about asserting a rendered attribute value. A number of other questions (e.g. here, here and here) deal with React/Jest/Enzyme but don't deal with retrieving attribute values.
After some digging, I found the following. The indicated line in the question:
const src = img.node.props.src;
can be simplified to the following:
const src = img.prop('src');
The documentation can be found here.
If someone knows of a non-Enzyme way of doing this, I'd still be interested in hearing about it.
For me, it worked as below
expect(companySelect.find('input').props()["disabled"]).toBe(true)
props() returns an object having all the attributes of the selector and then it can be browsed as an object.
Hope this helps too....
https://airbnb.io/enzyme/docs/api/ReactWrapper/props.html
The #testing-library/jest-dom library provides a custom matcher toHaveAttribute. After extending expect clause
import '#testing-library/jest-dom/extend-expect'
we can assert like
const rect = document.querySelector('[data-testid="my-rect"]')
expect(rect).toHaveAttribute('width', '256')
With React Test Utilities:
it('should have the logo image', () =>
const app = TestUtils.renderIntoDocument(<App/>);
var image = TestUtils.findRenderedDOMComponentWithTag(app, 'img');
expect(image.getDOMNode().getAttribute('src')).toEqual('logo.svg');
});
Enzyme tests looks much cleaner.
For me this worked
expect(component.find('button').props().disabled).toBeTruthy();
.node is not working After some hard work, I found the following is 100% related to answer for above question
const src = img.getElement().props.src;